// Copyright 2016 The prometheus-operator Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package prometheus

import (
	"context"
	"fmt"
	"reflect"
	"strconv"
	"strings"
	"testing"

	"github.com/kylelemons/godebug/pretty"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	appsv1 "k8s.io/api/apps/v1"
	v1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/intstr"
	"k8s.io/client-go/kubernetes/fake"
	"k8s.io/utils/ptr"

	monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
	"github.com/prometheus-operator/prometheus-operator/pkg/operator"
	prompkg "github.com/prometheus-operator/prometheus-operator/pkg/prometheus"
)

var defaultTestConfig = prompkg.Config{
	LocalHost:                  "localhost",
	ReloaderConfig:             operator.DefaultReloaderTestConfig.ReloaderConfig,
	PrometheusDefaultBaseImage: operator.DefaultPrometheusBaseImage,
	ThanosDefaultBaseImage:     operator.DefaultThanosBaseImage,
}

func makeStatefulSetFromPrometheus(p monitoringv1.Prometheus) (*appsv1.StatefulSet, error) {
	logger := prompkg.NewLogger()

	cg, err := prompkg.NewConfigGenerator(logger, &p)
	if err != nil {
		return nil, err
	}

	return makeStatefulSet(
		"test",
		&p,
		defaultTestConfig,
		cg,
		nil,
		"",
		0,
		&operator.ShardedSecret{})
}

func TestStatefulSetLabelingAndAnnotations(t *testing.T) {
	labels := map[string]string{
		"testlabel": "testlabelvalue",
	}
	annotations := map[string]string{
		"testannotation": "testannotationvalue",
		"kubectl.kubernetes.io/last-applied-configuration": "something",
		"kubectl.kubernetes.io/something":                  "something",
	}
	// kubectl annotations must not be on the statefulset so kubectl does
	// not manage the generated object
	expectedStatefulSetAnnotations := map[string]string{
		"prometheus-operator-input-hash": "",
		"testannotation":                 "testannotationvalue",
	}

	expectedStatefulSetLabels := map[string]string{
		"testlabel":                    "testlabelvalue",
		"operator.prometheus.io/name":  "",
		"operator.prometheus.io/shard": "0",
		"operator.prometheus.io/mode":  "server",
		"managed-by":                   "prometheus-operator",
	}

	expectedPodLabels := map[string]string{
		"prometheus":                   "",
		"app.kubernetes.io/name":       "prometheus",
		"app.kubernetes.io/version":    strings.TrimPrefix(operator.DefaultPrometheusVersion, "v"),
		"app.kubernetes.io/managed-by": "prometheus-operator",
		"app.kubernetes.io/instance":   "",
		"operator.prometheus.io/name":  "",
		"operator.prometheus.io/shard": "0",
	}

	sset, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{
		ObjectMeta: metav1.ObjectMeta{
			Labels:      labels,
			Annotations: annotations,
		},
	})
	require.NoError(t, err)

	require.Equalf(t, expectedStatefulSetLabels, sset.Labels, "Labels are not properly being propagated to the StatefulSet\n%s", pretty.Compare(expectedStatefulSetLabels, sset.Labels))
	require.Equalf(t, expectedStatefulSetAnnotations, sset.Annotations, "Annotations are not properly being propagated to the StatefulSet\n%s", pretty.Compare(expectedStatefulSetAnnotations, sset.Annotations))
	require.Equalf(t, expectedPodLabels, sset.Spec.Template.ObjectMeta.Labels, "Labels are not properly being propagated to the Pod\n%s", pretty.Compare(expectedPodLabels, sset.Spec.Template.ObjectMeta.Labels))
}

func TestPodLabelsAnnotations(t *testing.T) {
	annotations := map[string]string{
		"testannotation": "testvalue",
	}
	labels := map[string]string{
		"testlabel": "testvalue",
	}

	sset, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{
		ObjectMeta: metav1.ObjectMeta{},
		Spec: monitoringv1.PrometheusSpec{
			CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
				PodMetadata: &monitoringv1.EmbeddedObjectMetadata{
					Annotations: annotations,
					Labels:      labels,
				},
			},
		},
	})
	require.NoError(t, err)

	valLabel := sset.Spec.Template.ObjectMeta.Labels["testlabel"]
	require.Equal(t, "testvalue", valLabel, "Pod labels are not properly propagated")

	valAnnotation := sset.Spec.Template.ObjectMeta.Annotations["testannotation"]
	require.Equal(t, "testvalue", valAnnotation, "Pod annotations are not properly propagated")
}

func TestPodLabelsShouldNotBeSelectorLabels(t *testing.T) {
	labels := map[string]string{
		"testlabel": "testvalue",
	}
	sset, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{
		ObjectMeta: metav1.ObjectMeta{},
		Spec: monitoringv1.PrometheusSpec{
			CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
				PodMetadata: &monitoringv1.EmbeddedObjectMetadata{
					Labels: labels,
				},
			},
		},
	})
	require.NoError(t, err)

	require.NotEqual(t, "testvalue", sset.Spec.Selector.MatchLabels["testlabel"], "Pod Selector are not properly propagated")
}

func TestStatefulSetPVC(t *testing.T) {
	labels := map[string]string{
		"testlabel": "testlabelvalue",
	}
	annotations := map[string]string{
		"testannotation": "testannotationvalue",
	}

	storageClass := "storageclass"

	pvc := monitoringv1.EmbeddedPersistentVolumeClaim{
		EmbeddedObjectMetadata: monitoringv1.EmbeddedObjectMetadata{
			Annotations: annotations,
		},
		Spec: v1.PersistentVolumeClaimSpec{
			AccessModes:      []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
			StorageClassName: &storageClass,
		},
	}

	sset, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{
		ObjectMeta: metav1.ObjectMeta{
			Labels:      labels,
			Annotations: annotations,
		},
		Spec: monitoringv1.PrometheusSpec{
			CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
				Storage: &monitoringv1.StorageSpec{
					VolumeClaimTemplate: pvc,
				},
			},
		},
	})
	require.NoError(t, err)

	ssetPvc := sset.Spec.VolumeClaimTemplates[0]
	require.Equal(t, *pvc.Spec.StorageClassName, *ssetPvc.Spec.StorageClassName, "Error adding PVC Spec to StatefulSetSpec")
}

func TestStatefulSetEmptyDir(t *testing.T) {
	labels := map[string]string{
		"testlabel": "testlabelvalue",
	}
	annotations := map[string]string{
		"testannotation": "testannotationvalue",
	}

	emptyDir := v1.EmptyDirVolumeSource{
		Medium: v1.StorageMediumMemory,
	}

	sset, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{
		ObjectMeta: metav1.ObjectMeta{
			Labels:      labels,
			Annotations: annotations,
		},
		Spec: monitoringv1.PrometheusSpec{
			CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
				Storage: &monitoringv1.StorageSpec{
					EmptyDir: &emptyDir,
				},
			},
		},
	})
	require.NoError(t, err)

	ssetVolumes := sset.Spec.Template.Spec.Volumes
	require.NotNil(t, ssetVolumes[len(ssetVolumes)-1].VolumeSource.EmptyDir, "Error adding EmptyDir Spec to StatefulSetSpec")
	require.Equal(t, emptyDir.Medium, ssetVolumes[len(ssetVolumes)-1].VolumeSource.EmptyDir.Medium, "Error adding EmptyDir Spec to StatefulSetSpec")
}

func TestStatefulSetEphemeral(t *testing.T) {
	labels := map[string]string{
		"testlabel": "testlabelvalue",
	}
	annotations := map[string]string{
		"testannotation": "testannotationvalue",
	}

	storageClass := "storageclass"

	ephemeral := v1.EphemeralVolumeSource{
		VolumeClaimTemplate: &v1.PersistentVolumeClaimTemplate{
			Spec: v1.PersistentVolumeClaimSpec{
				AccessModes:      []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
				StorageClassName: &storageClass,
			},
		},
	}

	sset, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{
		ObjectMeta: metav1.ObjectMeta{
			Labels:      labels,
			Annotations: annotations,
		},
		Spec: monitoringv1.PrometheusSpec{
			CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
				Storage: &monitoringv1.StorageSpec{
					Ephemeral: &ephemeral,
				},
			},
		},
	})
	require.NoError(t, err)

	ssetVolumes := sset.Spec.Template.Spec.Volumes
	require.NotNil(t, ssetVolumes[len(ssetVolumes)-1].VolumeSource.Ephemeral, "Error adding Ephemeral Spec to StatefulSetSpec")
	require.Equal(t, ephemeral.VolumeClaimTemplate.Spec.StorageClassName, ssetVolumes[len(ssetVolumes)-1].VolumeSource.Ephemeral.VolumeClaimTemplate.Spec.StorageClassName, "Error adding Ephemeral Spec to StatefulSetSpec")
}

func TestStatefulSetVolumeInitial(t *testing.T) {
	p := monitoringv1.Prometheus{
		ObjectMeta: metav1.ObjectMeta{
			Name: "volume-init-test",
		},
		Spec: monitoringv1.PrometheusSpec{
			CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
				Secrets: []string{
					"test-secret1",
				},
			},
		},
	}

	expected := &appsv1.StatefulSet{
		Spec: appsv1.StatefulSetSpec{
			Template: v1.PodTemplateSpec{
				Spec: v1.PodSpec{
					Containers: []v1.Container{
						{
							VolumeMounts: []v1.VolumeMount{
								{
									Name:      "config-out",
									ReadOnly:  true,
									MountPath: "/etc/prometheus/config_out",
									SubPath:   "",
								},
								{
									Name:             "tls-assets",
									ReadOnly:         true,
									MountPath:        "/etc/prometheus/certs",
									SubPath:          "",
									MountPropagation: nil,
									SubPathExpr:      "",
								},
								{
									Name:      "prometheus-volume-init-test-db",
									ReadOnly:  false,
									MountPath: "/prometheus",
									SubPath:   "",
								},
								{
									Name:      "secret-test-secret1",
									ReadOnly:  true,
									MountPath: "/etc/prometheus/secrets/test-secret1",
									SubPath:   "",
								},
								{
									Name:      "rules-configmap-one",
									ReadOnly:  false,
									MountPath: "/etc/prometheus/rules/rules-configmap-one",
									SubPath:   "",
								},
								{
									Name:      "web-config",
									ReadOnly:  true,
									MountPath: "/etc/prometheus/web_config/web-config.yaml",
									SubPath:   "web-config.yaml",
								},
							},
						},
					},
					Volumes: []v1.Volume{
						{
							Name: "config",
							VolumeSource: v1.VolumeSource{
								Secret: &v1.SecretVolumeSource{
									SecretName: prompkg.ConfigSecretName(&p),
								},
							},
						},
						{
							Name: "tls-assets",
							VolumeSource: v1.VolumeSource{
								Projected: &v1.ProjectedVolumeSource{
									Sources: []v1.VolumeProjection{
										{
											Secret: &v1.SecretProjection{
												LocalObjectReference: v1.LocalObjectReference{
													Name: prompkg.TLSAssetsSecretName(&p) + "-0",
												},
											},
										},
									},
								},
							},
						},
						{
							Name: "config-out",
							VolumeSource: v1.VolumeSource{
								EmptyDir: &v1.EmptyDirVolumeSource{
									Medium: v1.StorageMediumMemory,
								},
							},
						},
						{
							Name: "secret-test-secret1",
							VolumeSource: v1.VolumeSource{
								Secret: &v1.SecretVolumeSource{
									SecretName: "test-secret1",
								},
							},
						},
						{
							Name: "rules-configmap-one",
							VolumeSource: v1.VolumeSource{
								ConfigMap: &v1.ConfigMapVolumeSource{
									LocalObjectReference: v1.LocalObjectReference{
										Name: "rules-configmap-one",
									},
								},
							},
						},
						{
							Name: "web-config",
							VolumeSource: v1.VolumeSource{
								Secret: &v1.SecretVolumeSource{
									SecretName: "prometheus-volume-init-test-web-config",
								},
							},
						},
						{
							Name: "prometheus-volume-init-test-db",
							VolumeSource: v1.VolumeSource{
								EmptyDir: &v1.EmptyDirVolumeSource{},
							},
						},
					},
				},
			},
		},
	}

	logger := prompkg.NewLogger()

	cg, err := prompkg.NewConfigGenerator(logger, &p)
	require.NoError(t, err)

	shardedSecret, err := operator.ReconcileShardedSecret(
		context.Background(),
		map[string][]byte{},
		fake.NewSimpleClientset(),
		&v1.Secret{
			ObjectMeta: metav1.ObjectMeta{
				Name:      prompkg.TLSAssetsSecretName(&p),
				Namespace: "test",
			},
		},
	)
	require.NoError(t, err)

	sset, err := makeStatefulSet(
		"volume-init-test",
		&p,
		defaultTestConfig,
		cg,
		[]string{"rules-configmap-one"},
		"",
		0,
		shardedSecret)
	require.NoError(t, err)

	require.Equalf(t, expected.Spec.Template.Spec.Volumes, sset.Spec.Template.Spec.Volumes, "expected volumes to match \n%s", pretty.Compare(expected.Spec.Template.Spec.Volumes, sset.Spec.Template.Spec.Volumes))
	require.Equalf(t, expected.Spec.Template.Spec.Containers[0].VolumeMounts, sset.Spec.Template.Spec.Containers[0].VolumeMounts, "expected volume mounts to match \n%s", pretty.Compare(expected.Spec.Template.Spec.Containers[0].VolumeMounts, sset.Spec.Template.Spec.Containers[0].VolumeMounts))
}

func TestAdditionalConfigMap(t *testing.T) {
	sset, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{
		Spec: monitoringv1.PrometheusSpec{
			CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
				ConfigMaps: []string{"test-cm1"},
			},
		},
	})
	require.NoError(t, err)

	cmVolumeFound := false
	for _, v := range sset.Spec.Template.Spec.Volumes {
		if strings.HasPrefix(v.Name, "configmap-test-cm1") {
			cmVolumeFound = true
		}
	}
	require.True(t, cmVolumeFound, "ConfigMap volume not found")

	cmMounted := false
	for _, v := range sset.Spec.Template.Spec.Containers[0].VolumeMounts {
		if strings.HasPrefix(v.Name, "configmap-test-cm1") && v.MountPath == "/etc/prometheus/configmaps/test-cm1" {
			cmMounted = true
		}
	}
	require.True(t, cmMounted, "ConfigMap volume not mounted")
}

func TestListenLocal(t *testing.T) {
	sset, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{
		Spec: monitoringv1.PrometheusSpec{
			CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
				ListenLocal: true,
			},
		},
	})
	require.NoError(t, err)

	found := false
	for _, flag := range sset.Spec.Template.Spec.Containers[0].Args {
		if flag == "--web.listen-address=127.0.0.1:9090" {
			found = true
		}
	}

	require.True(t, found, "Prometheus not listening on loopback when it should.")

	expectedProbeHandler := func(probePath string) v1.ProbeHandler {
		return v1.ProbeHandler{
			Exec: &v1.ExecAction{
				Command: []string{
					`sh`,
					`-c`,
					fmt.Sprintf(`if [ -x "$(command -v curl)" ]; then exec curl --fail %[1]s; elif [ -x "$(command -v wget)" ]; then exec wget -q -O /dev/null %[1]s; else exit 1; fi`, fmt.Sprintf("http://localhost:9090%s", probePath)),
				},
			},
		}
	}

	actualStartupProbe := sset.Spec.Template.Spec.Containers[0].StartupProbe
	expectedStartupProbe := &v1.Probe{
		ProbeHandler:     expectedProbeHandler("/-/ready"),
		TimeoutSeconds:   3,
		PeriodSeconds:    15,
		FailureThreshold: 60,
	}
	require.Equal(t, expectedStartupProbe, actualStartupProbe, "Startup probe doesn't match expected. \n\nExpected: %+v\n\nGot: %+v", expectedStartupProbe, actualStartupProbe)

	actualLivenessProbe := sset.Spec.Template.Spec.Containers[0].LivenessProbe
	expectedLivenessProbe := &v1.Probe{
		ProbeHandler:     expectedProbeHandler("/-/healthy"),
		TimeoutSeconds:   3,
		PeriodSeconds:    5,
		FailureThreshold: 6,
	}
	require.Equal(t, expectedLivenessProbe, actualLivenessProbe, "Liveness probe doesn't match expected. \n\nExpected: %+v\n\nGot: %+v", expectedLivenessProbe, actualLivenessProbe)

	actualReadinessProbe := sset.Spec.Template.Spec.Containers[0].ReadinessProbe
	expectedReadinessProbe := &v1.Probe{
		ProbeHandler:     expectedProbeHandler("/-/ready"),
		TimeoutSeconds:   3,
		PeriodSeconds:    5,
		FailureThreshold: 3,
	}
	require.Equal(t, expectedReadinessProbe, actualReadinessProbe, "Readiness probe doesn't match expected. \n\nExpected: %+v\n\nGot: %+v", expectedReadinessProbe, actualReadinessProbe)

	require.Empty(t, sset.Spec.Template.Spec.Containers[0].Ports, "Prometheus container should have 0 ports defined")
}

func TestListenTLS(t *testing.T) {
	sset, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{
		Spec: monitoringv1.PrometheusSpec{
			CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
				Web: &monitoringv1.PrometheusWebSpec{
					WebConfigFileFields: monitoringv1.WebConfigFileFields{
						TLSConfig: &monitoringv1.WebTLSConfig{
							KeySecret: v1.SecretKeySelector{
								LocalObjectReference: v1.LocalObjectReference{
									Name: "some-secret",
								},
							},
							Cert: monitoringv1.SecretOrConfigMap{
								ConfigMap: &v1.ConfigMapKeySelector{
									LocalObjectReference: v1.LocalObjectReference{
										Name: "some-configmap",
									},
								},
							},
						},
					},
				},
			},
			Thanos: &monitoringv1.ThanosSpec{},
		},
	})
	require.NoError(t, err)

	expectedProbeHandler := func(probePath string) v1.ProbeHandler {
		return v1.ProbeHandler{
			HTTPGet: &v1.HTTPGetAction{
				Path:   probePath,
				Port:   intstr.FromString("web"),
				Scheme: "HTTPS",
			},
		}
	}

	actualStartupProbe := sset.Spec.Template.Spec.Containers[0].StartupProbe
	expectedStartupProbe := &v1.Probe{
		ProbeHandler:     expectedProbeHandler("/-/ready"),
		TimeoutSeconds:   3,
		PeriodSeconds:    15,
		FailureThreshold: 60,
	}
	require.Equal(t, expectedStartupProbe, actualStartupProbe, "Startup probe doesn't match expected. \n\nExpected: %+v\n\nGot: %+v", expectedStartupProbe, actualStartupProbe)

	actualLivenessProbe := sset.Spec.Template.Spec.Containers[0].LivenessProbe
	expectedLivenessProbe := &v1.Probe{
		ProbeHandler:     expectedProbeHandler("/-/healthy"),
		TimeoutSeconds:   3,
		PeriodSeconds:    5,
		FailureThreshold: 6,
	}
	require.Equal(t, expectedLivenessProbe, actualLivenessProbe, "Liveness probe doesn't match expected. \n\nExpected: %+v\n\nGot: %+v", expectedLivenessProbe, actualLivenessProbe)

	actualReadinessProbe := sset.Spec.Template.Spec.Containers[0].ReadinessProbe
	expectedReadinessProbe := &v1.Probe{
		ProbeHandler:     expectedProbeHandler("/-/ready"),
		TimeoutSeconds:   3,
		PeriodSeconds:    5,
		FailureThreshold: 3,
	}
	require.Equal(t, expectedReadinessProbe, actualReadinessProbe, "Readiness probe doesn't match expected. \n\nExpected: %+v\n\nGot: %+v", expectedReadinessProbe, actualReadinessProbe)

	expectedConfigReloaderReloadURL := "--reload-url=https://localhost:9090/-/reload"
	reloadURLFound := false
	for _, arg := range sset.Spec.Template.Spec.Containers[1].Args {
		if arg == expectedConfigReloaderReloadURL {
			reloadURLFound = true
		}
	}
	require.True(t, reloadURLFound, "expected to find arg %s in config reloader", expectedConfigReloaderReloadURL)

	expectedThanosSidecarPrometheusURL := "--prometheus.url=https://localhost:9090/"
	prometheusURLFound := false
	for _, arg := range sset.Spec.Template.Spec.Containers[2].Args {
		if arg == expectedThanosSidecarPrometheusURL {
			prometheusURLFound = true
		}
	}
	require.True(t, prometheusURLFound, "expected to find arg %s in thanos sidecar", expectedThanosSidecarPrometheusURL)

	fmt.Println(sset.Spec.Template.Spec.Containers[2].Args)

	expectedArgsConfigReloader := []string{
		"--listen-address=:8080",
		"--web-config-file=/etc/prometheus/web_config/web-config.yaml",
		"--reload-url=https://localhost:9090/-/reload",
		"--config-file=/etc/prometheus/config/prometheus.yaml.gz",
		"--config-envsubst-file=/etc/prometheus/config_out/prometheus.env.yaml",
	}

	for _, c := range sset.Spec.Template.Spec.Containers {
		if c.Name == "config-reloader" {
			require.Equal(t, expectedArgsConfigReloader, c.Args, "expected container args are %s, but found %s", expectedArgsConfigReloader, c.Args)
		}
	}
}

func TestTagAndShaAndVersion(t *testing.T) {
	{
		sset, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{
			Spec: monitoringv1.PrometheusSpec{
				CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
					Version: "v2.3.2",
				},
				Tag: "my-unrelated-tag",
			},
		})
		require.NoError(t, err)

		image := sset.Spec.Template.Spec.Containers[0].Image
		expected := "quay.io/prometheus/prometheus:my-unrelated-tag"
		require.Equal(t, expected, image, "Unexpected container image.\n\nExpected: %s\n\nGot: %s", expected, image)
	}
	{
		sset, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{
			Spec: monitoringv1.PrometheusSpec{
				CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
					Version: "v2.3.2",
				},
				SHA: "7384a79f4b4991bf8269e7452390249b7c70bcdd10509c8c1c6c6e30e32fb324",
				Tag: "my-unrelated-tag",
			},
		})
		require.NoError(t, err)

		image := sset.Spec.Template.Spec.Containers[0].Image
		expected := "quay.io/prometheus/prometheus@sha256:7384a79f4b4991bf8269e7452390249b7c70bcdd10509c8c1c6c6e30e32fb324"
		require.Equal(t, expected, image, "Unexpected container image.\n\nExpected: %s\n\nGot: %s", expected, image)
	}
	// For tests which set monitoringv1.PrometheusSpec.Image, the result will be Image only. SHA, Tag, Version are not considered.
	{
		image := "my-reg/prometheus"
		sset, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{
			Spec: monitoringv1.PrometheusSpec{
				CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
					Version: "v2.3.2",
					Image:   &image,
				},
				SHA: "7384a79f4b4991bf8269e7452390249b7c70bcdd10509c8c1c6c6e30e32fb324",
				Tag: "my-unrelated-tag",
			},
		})
		require.NoError(t, err)

		resultImage := sset.Spec.Template.Spec.Containers[0].Image
		expected := image
		require.Equal(t, expected, resultImage, "Unexpected container image.\n\nExpected: %s\n\nGot: %s", expected, resultImage)
	}
	{
		image := "my-reg/prometheus:latest"
		sset, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{
			Spec: monitoringv1.PrometheusSpec{
				CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
					Version: "v2.3.2",
					Image:   &image,
				},
				SHA: "7384a79f4b4991bf8269e7452390249b7c70bcdd10509c8c1c6c6e30e32fb324",
				Tag: "my-unrelated-tag",
			},
		})
		require.NoError(t, err)

		resultImage := sset.Spec.Template.Spec.Containers[0].Image
		expected := image
		require.Equal(t, expected, resultImage, "Explicit image should have precedence. Unexpected container image.\n\nExpected: %s\n\nGot: %s", expected, resultImage)
	}
	{
		image := "my-reg/prometheus"
		sset, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{
			Spec: monitoringv1.PrometheusSpec{
				CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
					Version: "v2.3.2",
					Image:   &image,
				},
			},
		})
		require.NoError(t, err)

		resultImage := sset.Spec.Template.Spec.Containers[0].Image
		expected := image
		require.Equal(t, expected, resultImage, "Unexpected container image.\n\nExpected: %s\n\nGot: %s", expected, resultImage)
	}
	{
		image := "my-reg/prometheus"
		sset, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{
			Spec: monitoringv1.PrometheusSpec{
				CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
					Version: "v2.3.2",
					Image:   &image,
				},
				SHA: "7384a79f4b4991bf8269e7452390249b7c70bcdd10509c8c1c6c6e30e32fb324",
			},
		})
		require.NoError(t, err)

		resultImage := sset.Spec.Template.Spec.Containers[0].Image
		expected := image
		require.Equal(t, expected, resultImage, "Unexpected container image.\n\nExpected: %s\n\nGot: %s", expected, resultImage)
	}
	{
		image := "my-reg/prometheus"
		sset, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{
			Spec: monitoringv1.PrometheusSpec{
				CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
					Image: &image,
				},
			},
		})
		require.NoError(t, err)

		resultImage := sset.Spec.Template.Spec.Containers[0].Image
		expected := image
		require.Equal(t, expected, resultImage, "Unexpected container image.\n\nExpected: %s\n\nGot: %s", expected, resultImage)
	}
	{
		image := "my-reg/prometheus"
		sset, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{
			Spec: monitoringv1.PrometheusSpec{
				CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
					Image: &image,
				},
				SHA: "7384a79f4b4991bf8269e7452390249b7c70bcdd10509c8c1c6c6e30e32fb324",
			},
		})
		require.NoError(t, err)

		resultImage := sset.Spec.Template.Spec.Containers[0].Image
		expected := image
		require.Equal(t, expected, resultImage, "Unexpected container image.\n\nExpected: %s\n\nGot: %s", expected, resultImage)
	}
	{
		image := ""
		sset, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{
			Spec: monitoringv1.PrometheusSpec{
				CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
					Image: &image,
				},
				Tag: "my-unrelated-tag",
			},
		})
		require.NoError(t, err)

		resultImage := sset.Spec.Template.Spec.Containers[0].Image
		expected := "quay.io/prometheus/prometheus:my-unrelated-tag"
		require.Equal(t, expected, resultImage, "Unexpected container image.\n\nExpected: %s\n\nGot: %s", expected, resultImage)
	}
	{
		image := "my-reg/prometheus@sha256:7384a79f4b4991bf8269e7452390249b7c70bcdd10509c8c1c6c6e30e32fb325"
		sset, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{
			Spec: monitoringv1.PrometheusSpec{
				CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
					Image: &image,
				},
				SHA: "7384a79f4b4991bf8269e7452390249b7c70bcdd10509c8c1c6c6e30e32fb324",
				Tag: "my-unrelated-tag",
			},
		})
		require.NoError(t, err)

		resultImage := sset.Spec.Template.Spec.Containers[0].Image
		expected := "my-reg/prometheus@sha256:7384a79f4b4991bf8269e7452390249b7c70bcdd10509c8c1c6c6e30e32fb325"
		require.Equal(t, expected, resultImage, "Unexpected container image.\n\nExpected: %s\n\nGot: %s", expected, resultImage)
	}
}

func TestPrometheusDefaultBaseImageFlag(t *testing.T) {
	operatorConfig := prompkg.Config{
		ReloaderConfig:             defaultTestConfig.ReloaderConfig,
		PrometheusDefaultBaseImage: "nondefaultuseflag/quay.io/prometheus/prometheus",
		ThanosDefaultBaseImage:     "nondefaultuseflag/quay.io/thanos/thanos",
	}
	labels := map[string]string{
		"testlabel": "testlabelvalue",
	}
	annotations := map[string]string{
		"testannotation": "testannotationvalue",
	}

	logger := prompkg.NewLogger()
	p := monitoringv1.Prometheus{
		ObjectMeta: metav1.ObjectMeta{
			Labels:      labels,
			Annotations: annotations,
		},
	}

	cg, err := prompkg.NewConfigGenerator(logger, &p)
	require.NoError(t, err)

	sset, err := makeStatefulSet(
		"test",
		&p,
		operatorConfig,
		cg,
		nil,
		"",
		0,
		&operator.ShardedSecret{})
	require.NoError(t, err)

	image := sset.Spec.Template.Spec.Containers[0].Image
	expected := "nondefaultuseflag/quay.io/prometheus/prometheus" + ":" + operator.DefaultPrometheusVersion
	require.Equal(t, expected, image, "Unexpected container image.\n\nExpected: %s\n\nGot: %s", expected, image)
}

func TestThanosDefaultBaseImageFlag(t *testing.T) {
	thanosBaseImageConfig := prompkg.Config{
		ReloaderConfig:             defaultTestConfig.ReloaderConfig,
		PrometheusDefaultBaseImage: "nondefaultuseflag/quay.io/prometheus/prometheus",
		ThanosDefaultBaseImage:     "nondefaultuseflag/quay.io/thanos/thanos",
	}
	labels := map[string]string{
		"testlabel": "testlabelvalue",
	}
	annotations := map[string]string{
		"testannotation": "testannotationvalue",
	}
	logger := prompkg.NewLogger()
	p := monitoringv1.Prometheus{
		ObjectMeta: metav1.ObjectMeta{
			Labels:      labels,
			Annotations: annotations,
		},
		Spec: monitoringv1.PrometheusSpec{
			Thanos: &monitoringv1.ThanosSpec{},
		},
	}

	cg, err := prompkg.NewConfigGenerator(logger, &p)
	require.NoError(t, err)

	sset, err := makeStatefulSet(
		"test",
		&p,
		thanosBaseImageConfig,
		cg,
		nil,
		"",
		0,
		&operator.ShardedSecret{})
	require.NoError(t, err)

	image := sset.Spec.Template.Spec.Containers[2].Image
	expected := "nondefaultuseflag/quay.io/thanos/thanos" + ":" + operator.DefaultThanosVersion
	require.Equal(t, expected, image, "Unexpected container image.\n\nExpected: %s\n\nGot: %s", expected, image)
}

func TestThanosTagAndShaAndVersion(t *testing.T) {
	{
		thanosTag := "my-unrelated-tag"
		thanosVersion := "v0.1.0"
		sset, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{
			Spec: monitoringv1.PrometheusSpec{
				Thanos: &monitoringv1.ThanosSpec{
					Version: &thanosVersion,
					Tag:     &thanosTag,
				},
			},
		})
		require.NoError(t, err)

		image := sset.Spec.Template.Spec.Containers[2].Image
		expected := "quay.io/thanos/thanos:my-unrelated-tag"
		require.Equal(t, expected, image, "Unexpected container image.\n\nExpected: %s\n\nGot: %s", expected, image)
	}
	{
		thanosSHA := "7384a79f4b4991bf8269e7452390249b7c70bcdd10509c8c1c6c6e30e32fb324"
		thanosTag := "my-unrelated-tag"
		thanosVersion := "v0.1.0-rc.2"
		sset, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{
			Spec: monitoringv1.PrometheusSpec{
				Thanos: &monitoringv1.ThanosSpec{
					SHA:     &thanosSHA,
					Version: &thanosVersion,
					Tag:     &thanosTag,
				},
			},
		})
		require.NoError(t, err)

		image := sset.Spec.Template.Spec.Containers[2].Image
		expected := "quay.io/thanos/thanos@sha256:7384a79f4b4991bf8269e7452390249b7c70bcdd10509c8c1c6c6e30e32fb324"
		require.Equal(t, expected, image, "Unexpected container image.\n\nExpected: %s\n\nGot: %s", expected, image)
	}
	{
		thanosSHA := "7384a79f4b4991bf8269e7452390249b7c70bcdd10509c8c1c6c6e30e32fb324"
		thanosTag := "my-unrelated-tag"
		thanosVersion := "v0.1.0-rc.2"
		thanosImage := "my-registry/thanos:latest"
		sset, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{
			Spec: monitoringv1.PrometheusSpec{
				Thanos: &monitoringv1.ThanosSpec{
					SHA:     &thanosSHA,
					Version: &thanosVersion,
					Tag:     &thanosTag,
					Image:   &thanosImage,
				},
			},
		})
		require.NoError(t, err)

		image := sset.Spec.Template.Spec.Containers[2].Image
		expected := "my-registry/thanos:latest"
		require.Equal(t, expected, image, "Explicit Thanos image should have precedence. Unexpected container image.\n\nExpected: %s\n\nGot: %s", expected, image)
	}
}

func TestThanosResourcesNotSet(t *testing.T) {
	sset, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{
		Spec: monitoringv1.PrometheusSpec{
			Thanos: &monitoringv1.ThanosSpec{},
		},
	})
	require.NoError(t, err)

	res := sset.Spec.Template.Spec.Containers[2].Resources
	require.False(t, (res.Limits != nil || res.Requests != nil), "Unexpected resources defined. \n\nExpected: nil\n\nGot: %v, %v", res.Limits, res.Requests)
}

func TestThanosResourcesSet(t *testing.T) {
	expected := v1.ResourceRequirements{
		Limits: v1.ResourceList{
			v1.ResourceCPU:    resource.MustParse("125m"),
			v1.ResourceMemory: resource.MustParse("75Mi"),
		},
		Requests: v1.ResourceList{
			v1.ResourceCPU:    resource.MustParse("100m"),
			v1.ResourceMemory: resource.MustParse("50Mi"),
		},
	}
	sset, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{
		Spec: monitoringv1.PrometheusSpec{
			Thanos: &monitoringv1.ThanosSpec{
				Resources: expected,
			},
		},
	})
	require.NoError(t, err)

	actual := sset.Spec.Template.Spec.Containers[2].Resources
	require.Equal(t, expected, actual, "Unexpected resources defined. \n\nExpected: %v\n\nGot: %v", expected, actual)
}

func TestThanosNoObjectStorage(t *testing.T) {
	sset, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{
		Spec: monitoringv1.PrometheusSpec{
			Thanos: &monitoringv1.ThanosSpec{},
		},
	})
	require.NoError(t, err)

	require.Equal(t, "prometheus", sset.Spec.Template.Spec.Containers[0].Name, "expected 1st containers to be prometheus, got %s", sset.Spec.Template.Spec.Containers[0].Name)
	require.Equal(t, "thanos-sidecar", sset.Spec.Template.Spec.Containers[2].Name, "expected 3rd container to be thanos-sidecar, got %s", sset.Spec.Template.Spec.Containers[2].Name)

	for _, arg := range sset.Spec.Template.Spec.Containers[0].Args {
		require.False(t, strings.HasPrefix(arg, "--storage.tsdb.max-block-duration=2h"), "Prometheus compaction should be disabled")
	}

	for _, arg := range sset.Spec.Template.Spec.Containers[2].Args {
		require.False(t, strings.HasPrefix(arg, "--tsdb.path="), "--tsdb.path argument should not be given to the Thanos sidecar")
	}
}

func TestThanosObjectStorage(t *testing.T) {
	testKey := "thanos-config-secret-test"

	sset, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{
		Spec: monitoringv1.PrometheusSpec{
			Thanos: &monitoringv1.ThanosSpec{
				ObjectStorageConfig: &v1.SecretKeySelector{
					Key: testKey,
				},
				BlockDuration: "2h",
			},
		},
	})
	require.NoError(t, err)

	require.Equal(t, "prometheus", sset.Spec.Template.Spec.Containers[0].Name, "expected 1st containers to be prometheus, got %s", sset.Spec.Template.Spec.Containers[0].Name)
	require.Equal(t, "thanos-sidecar", sset.Spec.Template.Spec.Containers[2].Name, "expected 3rd containers to be thanos-sidecar, got %s", sset.Spec.Template.Spec.Containers[2].Name)

	var containsEnvVar bool
	for _, env := range sset.Spec.Template.Spec.Containers[2].Env {
		if env.Name == "OBJSTORE_CONFIG" {
			if env.ValueFrom.SecretKeyRef.Key == testKey {
				containsEnvVar = true
				break
			}
		}
	}
	require.True(t, containsEnvVar, "Thanos sidecar is missing expected OBJSTORE_CONFIG env var with correct value")

	{
		var containsArg bool
		const expectedArg = "--objstore.config=$(OBJSTORE_CONFIG)"
		for _, arg := range sset.Spec.Template.Spec.Containers[2].Args {
			if arg == expectedArg {
				containsArg = true
				break
			}
		}
		require.True(t, containsArg, "Thanos sidecar is missing expected argument: %s", expectedArg)
	}
	{
		var containsArg bool
		const expectedArg = "--storage.tsdb.max-block-duration=2h"
		for _, arg := range sset.Spec.Template.Spec.Containers[0].Args {
			if arg == expectedArg {
				containsArg = true
				break
			}
		}
		require.True(t, containsArg, "Prometheus is missing expected argument: %s", expectedArg)
	}

	{
		var found bool
		for _, arg := range sset.Spec.Template.Spec.Containers[2].Args {
			if strings.HasPrefix(arg, "--tsdb.path=") {
				found = true
				break
			}
		}
		require.True(t, found, "--tsdb.path argument should be given to the Thanos sidecar, got %q", strings.Join(sset.Spec.Template.Spec.Containers[2].Args, " "))
	}

	{
		var found bool
		for _, vol := range sset.Spec.Template.Spec.Containers[2].VolumeMounts {
			if vol.MountPath == prompkg.StorageDir {
				found = true
				break
			}
		}
		require.True(t, found, "Prometheus data volume should be mounted in the Thanos sidecar")
	}
}

func TestThanosObjectStorageFile(t *testing.T) {
	testPath := "/vault/secret/config.yaml"
	sset, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{
		Spec: monitoringv1.PrometheusSpec{
			Thanos: &monitoringv1.ThanosSpec{
				ObjectStorageConfigFile: &testPath,
				BlockDuration:           "2h",
			},
		},
	})
	require.NoError(t, err)

	{
		var containsArg bool
		expectedArg := "--objstore.config-file=" + testPath
		for _, container := range sset.Spec.Template.Spec.Containers {
			if container.Name == "thanos-sidecar" {
				for _, arg := range container.Args {
					if arg == expectedArg {
						containsArg = true
						break
					}
				}
			}
		}
		require.True(t, containsArg, "Thanos sidecar is missing expected argument: %s", expectedArg)
	}

	{
		var containsArg bool
		const expectedArg = "--storage.tsdb.max-block-duration=2h"
		for _, container := range sset.Spec.Template.Spec.Containers {
			if container.Name == "prometheus" {
				for _, arg := range container.Args {
					if arg == expectedArg {
						containsArg = true
						break
					}
				}
			}
		}
		require.True(t, containsArg, "Prometheus is missing expected argument: %s", expectedArg)
	}

	{
		var found bool
		for _, container := range sset.Spec.Template.Spec.Containers {
			if container.Name == "thanos-sidecar" {
				for _, arg := range container.Args {
					if strings.HasPrefix(arg, "--tsdb.path=") {
						found = true
						break
					}
				}
			}
		}
		require.True(t, found, "--tsdb.path argument should be given to the Thanos sidecar, got %q", strings.Join(sset.Spec.Template.Spec.Containers[2].Args, " "))
	}

	{
		var found bool
		for _, container := range sset.Spec.Template.Spec.Containers {
			if container.Name == "thanos-sidecar" {
				for _, vol := range container.VolumeMounts {
					if vol.MountPath == prompkg.StorageDir {
						found = true
						break
					}
				}
			}
		}
		require.True(t, found, "Prometheus data volume should be mounted in the Thanos sidecar")
	}
}

func TestThanosBlockDuration(t *testing.T) {
	testKey := "thanos-config-secret-test"

	sset, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{
		Spec: monitoringv1.PrometheusSpec{
			Thanos: &monitoringv1.ThanosSpec{
				BlockDuration: "1h",
				ObjectStorageConfig: &v1.SecretKeySelector{
					Key: testKey,
				},
			},
		},
	})
	require.NoError(t, err)

	found := false
	for _, arg := range sset.Spec.Template.Spec.Containers[0].Args {
		if arg == "--storage.tsdb.max-block-duration=1h" {
			found = true
		}
	}
	require.True(t, found, "Thanos BlockDuration arg change not found")
}

func TestThanosWithNamedPVC(t *testing.T) {
	testKey := "named-pvc"
	storageClass := "storageclass"

	pvc := monitoringv1.EmbeddedPersistentVolumeClaim{
		EmbeddedObjectMetadata: monitoringv1.EmbeddedObjectMetadata{
			Name: testKey,
		},
		Spec: v1.PersistentVolumeClaimSpec{
			AccessModes:      []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
			StorageClassName: &storageClass,
		},
	}

	sset, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{
		Spec: monitoringv1.PrometheusSpec{
			CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
				Storage: &monitoringv1.StorageSpec{
					VolumeClaimTemplate: pvc,
				},
			},
			Thanos: &monitoringv1.ThanosSpec{
				BlockDuration: "1h",
				ObjectStorageConfig: &v1.SecretKeySelector{
					Key: testKey,
				},
			},
		},
	})
	require.NoError(t, err)

	found := false
	for _, container := range sset.Spec.Template.Spec.Containers {
		if container.Name == "thanos-sidecar" {
			for _, vol := range container.VolumeMounts {
				if vol.Name == testKey {
					found = true
				}
			}
		}
	}
	require.True(t, found, "VolumeClaimTemplate name not found on thanos-sidecar volumeMounts")
}

func TestThanosTracing(t *testing.T) {
	testKey := "thanos-config-secret-test"

	sset, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{
		Spec: monitoringv1.PrometheusSpec{
			Thanos: &monitoringv1.ThanosSpec{
				TracingConfig: &v1.SecretKeySelector{
					Key: testKey,
				},
			},
		},
	})
	require.NoError(t, err)

	require.Equal(t, "prometheus", sset.Spec.Template.Spec.Containers[0].Name, "expected 1st containers to be prometheus, got %s", sset.Spec.Template.Spec.Containers[0].Name)
	require.Equal(t, "thanos-sidecar", sset.Spec.Template.Spec.Containers[2].Name, "expected 3rd containers to be thanos-sidecar, got %s", sset.Spec.Template.Spec.Containers[2].Name)

	var containsEnvVar bool
	for _, env := range sset.Spec.Template.Spec.Containers[2].Env {
		if env.Name == "TRACING_CONFIG" {
			if env.ValueFrom.SecretKeyRef.Key == testKey {
				containsEnvVar = true
				break
			}
		}
	}
	require.True(t, containsEnvVar, "Thanos sidecar is missing expected TRACING_CONFIG env var with correct value")

	{
		var containsArg bool
		const expectedArg = "--tracing.config=$(TRACING_CONFIG)"
		for _, arg := range sset.Spec.Template.Spec.Containers[2].Args {
			if arg == expectedArg {
				containsArg = true
				break
			}
		}
		require.True(t, containsArg, "Thanos sidecar is missing expected argument: %s", expectedArg)
	}
}

func TestThanosSideCarVolumes(t *testing.T) {
	testVolume := "test-volume"
	testVolumeMountPath := "/prometheus/thanos-sidecar"
	sset, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{
		Spec: monitoringv1.PrometheusSpec{
			CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
				Volumes: []v1.Volume{
					{
						Name: testVolume,
						VolumeSource: v1.VolumeSource{
							EmptyDir: &v1.EmptyDirVolumeSource{},
						},
					},
				},
			},
			Thanos: &monitoringv1.ThanosSpec{
				VolumeMounts: []v1.VolumeMount{
					{
						Name:      testVolume,
						MountPath: testVolumeMountPath,
					},
				},
			},
		},
	})
	require.NoError(t, err)

	var containsVolume bool
	for _, volume := range sset.Spec.Template.Spec.Volumes {
		if volume.Name == testVolume {
			containsVolume = true
			break
		}
	}
	require.True(t, containsVolume, "Thanos sidecar volume is missing expected volume: %s", testVolume)

	var containsVolumeMount bool
	for _, container := range sset.Spec.Template.Spec.Containers {
		if container.Name == "thanos-sidecar" {
			for _, volumeMount := range container.VolumeMounts {
				if volumeMount.Name == testVolume && volumeMount.MountPath == testVolumeMountPath {
					containsVolumeMount = true
					break
				}
			}
		}
	}

	require.True(t, containsVolumeMount, "expected thanos sidecar volume mounts to match")
}

func TestRetentionAndRetentionSize(t *testing.T) {
	tests := []struct {
		version                    string
		specRetention              monitoringv1.Duration
		specRetentionSize          monitoringv1.ByteSize
		expectedRetentionArg       string
		expectedRetentionSizeArg   string
		shouldContainRetention     bool
		shouldContainRetentionSize bool
	}{
		{"v2.5.0", "", "", "--storage.tsdb.retention=24h", "--storage.tsdb.retention.size=", true, false},
		{"v2.5.0", "1d", "", "--storage.tsdb.retention=1d", "--storage.tsdb.retention.size=", true, false},
		{"v2.5.0", "", "512MB", "--storage.tsdb.retention=24h", "--storage.tsdb.retention.size=512MB", true, false},
		{"v2.5.0", "1d", "512MB", "--storage.tsdb.retention=1d", "--storage.tsdb.retention.size=512MB", true, false},
		{"v2.7.0", "", "", "--storage.tsdb.retention.time=24h", "--storage.tsdb.retention.size=", true, false},
		{"v2.7.0", "1d", "", "--storage.tsdb.retention.time=1d", "--storage.tsdb.retention.size=", true, false},
		{"v2.7.0", "", "512MB", "--storage.tsdb.retention.time=24h", "--storage.tsdb.retention.size=512MB", false, true},
		{"v2.7.0", "1d", "512MB", "--storage.tsdb.retention.time=1d", "--storage.tsdb.retention.size=512MB", true, true},
	}

	for _, test := range tests {
		sset, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{
			Spec: monitoringv1.PrometheusSpec{
				CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
					Version: test.version,
				},
				Retention:     test.specRetention,
				RetentionSize: test.specRetentionSize,
			},
		})
		require.NoError(t, err)

		promArgs := sset.Spec.Template.Spec.Containers[0].Args
		retentionFlag := strings.Split(test.expectedRetentionArg, "=")[0]
		foundRetentionFlag := false
		foundRetentionSizeFlag := false
		foundRetention := false
		foundRetentionSize := false
		for _, flag := range promArgs {
			if flag == test.expectedRetentionArg {
				foundRetention = true
			} else if flag == test.expectedRetentionSizeArg {
				foundRetentionSize = true
			}

			if strings.HasPrefix(flag, retentionFlag) {
				foundRetentionFlag = true
			} else if strings.HasPrefix(flag, "--storage.tsdb.retention.size") {
				foundRetentionSizeFlag = true
			}
		}

		if test.shouldContainRetention {
			require.True(t, (foundRetention && foundRetentionFlag))
		}

		if test.shouldContainRetentionSize {
			require.True(t, (foundRetentionSize && foundRetentionSizeFlag))
		}
	}
}

func TestReplicasConfigurationWithSharding(t *testing.T) {
	testConfig := prompkg.Config{
		ReloaderConfig:             defaultTestConfig.ReloaderConfig,
		PrometheusDefaultBaseImage: "quay.io/prometheus/prometheus",
		ThanosDefaultBaseImage:     "quay.io/thanos/thanos:v0.7.0",
	}
	replicas := int32(2)
	shards := int32(3)
	logger := prompkg.NewLogger()
	p := monitoringv1.Prometheus{
		Spec: monitoringv1.PrometheusSpec{
			CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
				Replicas: &replicas,
				Shards:   &shards,
			},
		},
	}

	cg, err := prompkg.NewConfigGenerator(logger, &p)
	require.NoError(t, err)

	sset, err := makeStatefulSet(
		"test",
		&p,
		testConfig,
		cg,
		nil,
		"",
		1,
		&operator.ShardedSecret{})
	require.NoError(t, err)

	require.Equal(t, int32(2), *sset.Spec.Replicas, "Unexpected replicas configuration.")

	found := false
	for _, c := range sset.Spec.Template.Spec.Containers {
		if c.Name == "config-reloader" {
			for _, env := range c.Env {
				if env.Name == "SHARD" && env.Value == "1" {
					found = true
				}
			}
		}
	}
	require.True(t, found, "Shard.")
}

func TestSidecarResources(t *testing.T) {
	operator.TestSidecarsResources(t, func(reloaderConfig operator.ContainerConfig) *appsv1.StatefulSet {
		testConfig := prompkg.Config{
			ReloaderConfig:             reloaderConfig,
			PrometheusDefaultBaseImage: defaultTestConfig.PrometheusDefaultBaseImage,
			ThanosDefaultBaseImage:     defaultTestConfig.ThanosDefaultBaseImage,
		}
		logger := prompkg.NewLogger()
		p := monitoringv1.Prometheus{
			Spec: monitoringv1.PrometheusSpec{},
		}

		cg, err := prompkg.NewConfigGenerator(logger, &p)
		require.NoError(t, err)

		sset, err := makeStatefulSet(
			"test",
			&p,
			testConfig,
			cg,
			nil,
			"",
			0,
			&operator.ShardedSecret{})
		require.NoError(t, err)
		return sset
	})
}

func TestAdditionalContainers(t *testing.T) {
	// The base to compare everything against
	baseSet, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{})
	require.NoError(t, err)

	// Add an extra container
	addSset, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{
		Spec: monitoringv1.PrometheusSpec{
			CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
				Containers: []v1.Container{
					{
						Name: "extra-container",
					},
				},
			},
		},
	})
	require.NoError(t, err)

	require.Len(t, addSset.Spec.Template.Spec.Containers, len(baseSet.Spec.Template.Spec.Containers)+1, "container count mismatch")

	// Adding a new container with the same name results in a merge and just one container
	const existingContainerName = "prometheus"
	const containerImage = "madeUpContainerImage"
	modSset, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{
		Spec: monitoringv1.PrometheusSpec{
			CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
				Containers: []v1.Container{
					{
						Name:  existingContainerName,
						Image: containerImage,
					},
				},
			},
		},
	})
	require.NoError(t, err)

	require.Equal(t, len(baseSet.Spec.Template.Spec.Containers), len(modSset.Spec.Template.Spec.Containers), "container count mismatch. container %s was added instead of merged", existingContainerName)

	// Check that adding a container with an existing name results in a single patched container.
	for _, c := range modSset.Spec.Template.Spec.Containers {
		require.False(t, (c.Name == existingContainerName && c.Image != containerImage), "expected container %s to have the image %s but got %s", existingContainerName, containerImage, c.Image)
	}
}

func TestWALCompression(t *testing.T) {
	var (
		tr = true
		fa = false
	)
	tests := []struct {
		version       string
		enabled       *bool
		expectedArg   string
		shouldContain bool
	}{
		// Nil should not have either flag.
		{"v2.10.0", nil, "--no-storage.tsdb.wal-compression", false},
		{"v2.10.0", nil, "--storage.tsdb.wal-compression", false},
		{"v2.10.0", &fa, "--no-storage.tsdb.wal-compression", false},
		{"v2.10.0", &tr, "--storage.tsdb.wal-compression", false},
		{"v2.11.0", nil, "--no-storage.tsdb.wal-compression", false},
		{"v2.11.0", nil, "--storage.tsdb.wal-compression", false},
		{"v2.11.0", &fa, "--no-storage.tsdb.wal-compression", true},
		{"v2.11.0", &tr, "--storage.tsdb.wal-compression", true},
	}

	for _, test := range tests {
		sset, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{
			Spec: monitoringv1.PrometheusSpec{
				CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
					Version:        test.version,
					WALCompression: test.enabled,
				},
			},
		})
		require.NoError(t, err)

		promArgs := sset.Spec.Template.Spec.Containers[0].Args
		found := false
		for _, flag := range promArgs {
			if flag == test.expectedArg {
				found = true
				break
			}
		}

		require.Equal(t, test.shouldContain, found)
	}
}

func TestTSDBAllowOverlappingBlocks(t *testing.T) {
	expectedArg := "--storage.tsdb.allow-overlapping-blocks"
	tests := []struct {
		version       string
		enabled       bool
		shouldContain bool
	}{
		{"v2.10.0", true, false},
		{"v2.11.0", true, true},
		{"v2.38.0", true, true},
		{"v2.39.0", true, false},
	}

	for _, test := range tests {
		sset, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{
			Spec: monitoringv1.PrometheusSpec{
				AllowOverlappingBlocks: test.enabled,
				CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
					Version: test.version,
				},
			},
		})
		require.NoError(t, err)

		promArgs := sset.Spec.Template.Spec.Containers[0].Args
		found := false
		for _, flag := range promArgs {
			if flag == expectedArg {
				found = true
				break
			}
		}

		require.Equal(t, test.shouldContain, found)
	}
}

func TestTSDBAllowOverlappingCompaction(t *testing.T) {
	expectedArg := "--storage.tsdb.allow-overlapping-compaction"
	tests := []struct {
		name                    string
		version                 string
		outOfOrderTimeWindow    monitoringv1.Duration
		objectStorageConfigFile *string
		shouldContain           bool
	}{
		{
			name:          "Prometheus version less than or equal to v2.55.0",
			version:       "v2.54.0",
			shouldContain: false,
		},
		{
			name:          "outOfOrderTimeWindow equal to 0s",
			version:       "v2.55.0",
			shouldContain: false,
		},
		{
			name:                    "Thanos is not object storage",
			version:                 "v2.55.0",
			outOfOrderTimeWindow:    "1s",
			objectStorageConfigFile: nil,
			shouldContain:           false,
		},
		{
			name:                    "Verify AllowOverlappingCompaction",
			version:                 "v2.55.0",
			outOfOrderTimeWindow:    "1s",
			objectStorageConfigFile: ptr.To("/etc/thanos.cfg"),
			shouldContain:           true,
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			sset, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{
				Spec: monitoringv1.PrometheusSpec{
					CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
						Version: test.version,
						TSDB: &monitoringv1.TSDBSpec{
							OutOfOrderTimeWindow: ptr.To(test.outOfOrderTimeWindow),
						},
					},
					Thanos: &monitoringv1.ThanosSpec{
						ListenLocal:             true,
						ObjectStorageConfigFile: test.objectStorageConfigFile,
					},
				},
			})
			require.NoError(t, err)

			promArgs := sset.Spec.Template.Spec.Containers[0].Args
			found := false
			for _, flag := range promArgs {
				if flag == expectedArg {
					found = true
					break
				}
			}

			require.Equal(t, test.shouldContain, found)
		})
	}
}

func TestThanosListenLocal(t *testing.T) {
	for _, tc := range []struct {
		spec     monitoringv1.ThanosSpec
		expected []string
	}{
		{
			spec: monitoringv1.ThanosSpec{
				ListenLocal: true,
			},
			expected: []string{
				"--grpc-address=127.0.0.1:10901",
				"--http-address=127.0.0.1:10902",
			},
		},
		{
			spec: monitoringv1.ThanosSpec{
				GRPCListenLocal: true,
			},
			expected: []string{
				"--grpc-address=127.0.0.1:10901",
				"--http-address=:10902",
			},
		},
		{
			spec: monitoringv1.ThanosSpec{
				HTTPListenLocal: true,
			},
			expected: []string{
				"--grpc-address=:10901",
				"--http-address=127.0.0.1:10902",
			},
		},
		{
			spec: monitoringv1.ThanosSpec{},
			expected: []string{
				"--grpc-address=:10901",
				"--http-address=:10902",
			},
		},
	} {
		t.Run("", func(t *testing.T) {
			sset, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{
				Spec: monitoringv1.PrometheusSpec{
					Thanos: &tc.spec,
				},
			})
			require.NoError(t, err)

			for _, exp := range tc.expected {
				var found bool
				for _, flag := range sset.Spec.Template.Spec.Containers[2].Args {
					if flag == exp {
						found = true
						break
					}
				}

				require.True(t, found, "Expecting argument %q but not found in %v", exp, sset.Spec.Template.Spec.Containers[2].Args)
			}
		})
	}
}

func TestTerminationPolicy(t *testing.T) {
	sset, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{Spec: monitoringv1.PrometheusSpec{}})
	require.NoError(t, err)

	for _, c := range sset.Spec.Template.Spec.Containers {
		require.Equal(t, v1.TerminationMessageFallbackToLogsOnError, c.TerminationMessagePolicy, "Unexpected TermintationMessagePolicy. Expected %v got %v", v1.TerminationMessageFallbackToLogsOnError, c.TerminationMessagePolicy)
	}
}

func TestEnableFeaturesWithOneFeature(t *testing.T) {
	sset, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{
		Spec: monitoringv1.PrometheusSpec{
			CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
				EnableFeatures: []monitoringv1.EnableFeature{"exemplar-storage"},
			},
		},
	})
	require.NoError(t, err)

	found := false
	for _, flag := range sset.Spec.Template.Spec.Containers[0].Args {
		if flag == "--enable-feature=exemplar-storage" {
			found = true
		}
	}

	require.True(t, found, "Prometheus enabled feature is not correctly set.")
}

func TestEnableFeaturesWithMultipleFeature(t *testing.T) {
	sset, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{
		Spec: monitoringv1.PrometheusSpec{
			CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
				EnableFeatures: []monitoringv1.EnableFeature{"exemplar-storage1", "exemplar-storage2"},
			},
		},
	})
	require.NoError(t, err)

	found := false
	for _, flag := range sset.Spec.Template.Spec.Containers[0].Args {
		if flag == "--enable-feature=exemplar-storage1,exemplar-storage2" {
			found = true
		}
	}

	require.True(t, found, "Prometheus enabled features are not correctly set.")
}

func TestWebPageTitle(t *testing.T) {
	pageTitle := "my-page-title"
	sset, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{
		Spec: monitoringv1.PrometheusSpec{
			CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
				Web: &monitoringv1.PrometheusWebSpec{
					PageTitle: &pageTitle,
				},
			},
		},
	})
	require.NoError(t, err)

	found := false
	for _, flag := range sset.Spec.Template.Spec.Containers[0].Args {
		if flag == "--web.page-title=my-page-title" {
			found = true
		}
	}

	require.True(t, found, "Prometheus web page title is not correctly set.")
}

func TestMaxConnections(t *testing.T) {
	maxConnections := int32(600)
	sset, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{
		Spec: monitoringv1.PrometheusSpec{
			CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
				Web: &monitoringv1.PrometheusWebSpec{
					MaxConnections: &maxConnections,
				},
			},
		},
	})
	require.NoError(t, err)

	found := false
	for _, flag := range sset.Spec.Template.Spec.Containers[0].Args {
		if flag == "--web.max-connections=600" {
			found = true
		}
	}

	require.True(t, found, "Prometheus web max connections is not correctly set.")
}

func TestExpectedStatefulSetShardNames(t *testing.T) {
	replicas := int32(2)
	shards := int32(3)
	res := prompkg.ExpectedStatefulSetShardNames(&monitoringv1.Prometheus{
		ObjectMeta: metav1.ObjectMeta{
			Name: "test",
		},
		Spec: monitoringv1.PrometheusSpec{
			CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
				Shards:   &shards,
				Replicas: &replicas,
			},
		},
	})

	expected := []string{
		"prometheus-test",
		"prometheus-test-shard-1",
		"prometheus-test-shard-2",
	}

	for i, name := range expected {
		require.Equal(t, name, res[i], "Unexpected StatefulSet shard name")
	}
}

func TestExpectStatefulSetMinReadySeconds(t *testing.T) {
	sset, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{
		Spec: monitoringv1.PrometheusSpec{},
	})
	require.NoError(t, err)

	// assert defaults to zero if nil
	require.Equal(t, int32(0), sset.Spec.MinReadySeconds, "expected MinReadySeconds to be zero but got %d", sset.Spec.MinReadySeconds)

	var expect uint32 = 5
	sset, err = makeStatefulSetFromPrometheus(monitoringv1.Prometheus{
		Spec: monitoringv1.PrometheusSpec{
			CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
				MinReadySeconds: &expect,
			},
		},
	})
	require.NoError(t, err)

	require.Equal(t, int32(expect), sset.Spec.MinReadySeconds, "expected MinReadySeconds to be %d but got %d", expect, sset.Spec.MinReadySeconds)
}

func TestConfigReloader(t *testing.T) {
	expectedShardNum := 0
	logger := prompkg.NewLogger()
	p := monitoringv1.Prometheus{}

	cg, err := prompkg.NewConfigGenerator(logger, &p)
	require.NoError(t, err)

	sset, err := makeStatefulSet(
		"test",
		&p,
		defaultTestConfig,
		cg,
		nil,
		"",
		int32(expectedShardNum),
		&operator.ShardedSecret{})
	require.NoError(t, err)

	expectedArgsConfigReloader := []string{
		"--listen-address=:8080",
		"--reload-url=http://localhost:9090/-/reload",
		"--config-file=/etc/prometheus/config/prometheus.yaml.gz",
		"--config-envsubst-file=/etc/prometheus/config_out/prometheus.env.yaml",
	}

	for _, c := range sset.Spec.Template.Spec.Containers {
		if c.Name == "config-reloader" {
			require.Equal(t, expectedArgsConfigReloader, c.Args, "expectd container args are %s, but found %s", expectedArgsConfigReloader, c.Args)
			for _, env := range c.Env {
				require.False(t, (env.Name == "SHARD" && !reflect.DeepEqual(env.Value, strconv.Itoa(expectedShardNum))), "expectd shard value is %s, but found %s", strconv.Itoa(expectedShardNum), env.Value)
			}
		}
	}

	expectedArgsInitConfigReloader := []string{
		"--watch-interval=0",
		"--listen-address=:8080",
		"--config-file=/etc/prometheus/config/prometheus.yaml.gz",
		"--config-envsubst-file=/etc/prometheus/config_out/prometheus.env.yaml",
	}

	for _, c := range sset.Spec.Template.Spec.Containers {
		if c.Name == "init-config-reloader" {
			require.Equal(t, expectedArgsInitConfigReloader, c.Args, "expectd init container args are %s, but found %s", expectedArgsInitConfigReloader, c.Args)
			for _, env := range c.Env {
				require.False(t, (env.Name == "SHARD" && !reflect.DeepEqual(env.Value, strconv.Itoa(expectedShardNum))), "expectd shard value is %s, but found %s", strconv.Itoa(expectedShardNum), env.Value)
			}
		}
	}
}

func TestConfigReloaderWithSignal(t *testing.T) {
	expectedShardNum := 0
	logger := prompkg.NewLogger()
	p := monitoringv1.Prometheus{
		Spec: monitoringv1.PrometheusSpec{
			CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
				ReloadStrategy: func(r monitoringv1.ReloadStrategyType) *monitoringv1.ReloadStrategyType { return &r }(monitoringv1.ProcessSignalReloadStrategyType),
			},
		},
	}

	cg, err := prompkg.NewConfigGenerator(logger, &p)
	require.NoError(t, err)

	sset, err := makeStatefulSet(
		"test",
		&p,
		defaultTestConfig,
		cg,
		nil,
		"",
		int32(expectedShardNum),
		&operator.ShardedSecret{})
	require.NoError(t, err)

	expectedArgsConfigReloader := []string{
		"--listen-address=:8080",
		"--reload-method=signal",
		"--runtimeinfo-url=http://localhost:9090/api/v1/status/runtimeinfo",
		"--config-file=/etc/prometheus/config/prometheus.yaml.gz",
		"--config-envsubst-file=/etc/prometheus/config_out/prometheus.env.yaml",
	}

	for _, c := range sset.Spec.Template.Spec.Containers {
		switch c.Name {
		case "config-reloader":
			require.Equal(t, expectedArgsConfigReloader, c.Args)
			for _, env := range c.Env {
				if env.Name == "SHARD" {
					require.Equal(t, strconv.Itoa(expectedShardNum), env.Value)
				}
			}

		case "prometheus":
			require.NotContains(t, c.Args, "--web.enable-lifecycle")
		}
	}

	expectedArgsInitConfigReloader := []string{
		"--watch-interval=0",
		"--listen-address=:8081",
		"--config-file=/etc/prometheus/config/prometheus.yaml.gz",
		"--config-envsubst-file=/etc/prometheus/config_out/prometheus.env.yaml",
	}

	for _, c := range sset.Spec.Template.Spec.InitContainers {
		if c.Name == "init-config-reloader" {
			require.Equal(t, expectedArgsInitConfigReloader, c.Args)
			for _, env := range c.Env {
				if env.Name == "SHARD" {
					require.Equal(t, strconv.Itoa(expectedShardNum), env.Value)
				}
			}
		}
	}
}

func TestThanosGetConfigInterval(t *testing.T) {
	sset, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{
		Spec: monitoringv1.PrometheusSpec{
			Thanos: &monitoringv1.ThanosSpec{
				GetConfigInterval: "1m",
			},
		},
	})
	require.NoError(t, err)

	found := false
	for _, container := range sset.Spec.Template.Spec.Containers {
		if container.Name == "thanos-sidecar" {
			for _, flag := range container.Args {
				if flag == "--prometheus.get_config_interval=1m" {
					found = true
				}
			}
		}
	}

	require.True(t, found, "Sidecar get_config_interval is not set when it should.")
}

func TestThanosGetConfigTimeout(t *testing.T) {
	sset, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{
		Spec: monitoringv1.PrometheusSpec{
			Thanos: &monitoringv1.ThanosSpec{
				GetConfigTimeout: "30s",
			},
		},
	})
	require.NoError(t, err)

	found := false
	for _, container := range sset.Spec.Template.Spec.Containers {
		if container.Name == "thanos-sidecar" {
			for _, flag := range container.Args {
				if flag == "--prometheus.get_config_timeout=30s" {
					found = true
				}
			}
		}
	}

	require.True(t, found, "Sidecar get_config_timeout is not set when it should.")
}

func TestThanosReadyTimeout(t *testing.T) {
	sset, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{
		Spec: monitoringv1.PrometheusSpec{
			Thanos: &monitoringv1.ThanosSpec{
				ReadyTimeout: "20m",
			},
		},
	})
	require.NoError(t, err)

	found := false
	for _, container := range sset.Spec.Template.Spec.Containers {
		if container.Name == "thanos-sidecar" {
			for _, flag := range container.Args {
				if flag == "--prometheus.ready_timeout=20m" {
					found = true
				}
			}
		}
	}

	require.True(t, found, "Sidecar ready timeout not set when it should.")
}

func TestQueryLogFileVolumeMountPresent(t *testing.T) {
	sset, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{
		Spec: monitoringv1.PrometheusSpec{
			QueryLogFile: "test.log",
		},
	})
	require.NoError(t, err)

	found := false
	for _, volume := range sset.Spec.Template.Spec.Volumes {
		if volume.Name == "query-log-file" {
			found = true
		}
	}

	require.True(t, found, "Volume for query log file not found.")

	found = false
	for _, container := range sset.Spec.Template.Spec.Containers {
		if container.Name == "prometheus" {
			for _, vm := range container.VolumeMounts {
				if vm.Name == "query-log-file" {
					found = true
				}
			}
		}
	}

	require.True(t, found, "Query log file not mounted.")
}

func TestQueryLogFileVolumeMountNotPresent(t *testing.T) {
	// An emptyDir is only mounted by the Operator if the given
	// path is only a base filename.
	sset, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{
		Spec: monitoringv1.PrometheusSpec{
			QueryLogFile: "/tmp/test.log",
		},
	})
	require.NoError(t, err)

	found := false
	for _, volume := range sset.Spec.Template.Spec.Volumes {
		if volume.Name == "query-log-file" {
			found = true
		}
	}

	require.False(t, found, "Volume for query log file found, when it shouldn't be.")

	found = false
	for _, container := range sset.Spec.Template.Spec.Containers {
		if container.Name == "prometheus" {
			for _, vm := range container.VolumeMounts {
				if vm.Name == "query-log-file" {
					found = true
				}
			}
		}
	}

	require.False(t, found, "Query log file mounted, when it shouldn't be.")
}

func TestRemoteWriteReceiver(t *testing.T) {
	for _, tc := range []struct {
		version                   string
		enableRemoteWriteReceiver bool
		messageVersions           []monitoringv1.RemoteWriteMessageVersion

		expectedRemoteWriteReceiverFlag bool
		expectedMessageVersions         string
	}{
		// Remote write receiver not supported.
		{
			version:                   "2.32.0",
			enableRemoteWriteReceiver: true,
		},
		// Remote write receiver supported starting with v2.33.0.
		{
			version:                         "2.33.0",
			enableRemoteWriteReceiver:       true,
			expectedRemoteWriteReceiverFlag: true,
		},
		// Remote write receiver supported but not enabled.
		{
			version:                         "2.33.0",
			enableRemoteWriteReceiver:       false,
			expectedRemoteWriteReceiverFlag: false,
		},
		// Test higher version from which feature available
		{
			version:                         "2.33.5",
			enableRemoteWriteReceiver:       true,
			expectedRemoteWriteReceiverFlag: true,
		},
		// RemoteWriteMessageVersions not supported.
		{
			version:                         "2.53.0",
			enableRemoteWriteReceiver:       true,
			expectedRemoteWriteReceiverFlag: true,
			messageVersions: []monitoringv1.RemoteWriteMessageVersion{
				monitoringv1.RemoteWriteMessageVersion2_0,
			},
		},
		// RemoteWriteMessageVersions supported and set to one value.
		{
			version:                   "2.54.0",
			enableRemoteWriteReceiver: true,
			messageVersions: []monitoringv1.RemoteWriteMessageVersion{
				monitoringv1.RemoteWriteMessageVersion2_0,
			},
			expectedRemoteWriteReceiverFlag: true,
			expectedMessageVersions:         "io.prometheus.write.v2.Request",
		},
		// RemoteWriteMessageVersions supported and set to 2 values.
		{
			version:                   "2.54.0",
			enableRemoteWriteReceiver: true,
			messageVersions: []monitoringv1.RemoteWriteMessageVersion{
				monitoringv1.RemoteWriteMessageVersion1_0,
				monitoringv1.RemoteWriteMessageVersion2_0,
			},
			expectedRemoteWriteReceiverFlag: true,
			expectedMessageVersions:         "prometheus.WriteRequest,io.prometheus.write.v2.Request",
		},
	} {
		t.Run(tc.version, func(t *testing.T) {
			p := monitoringv1.Prometheus{
				Spec: monitoringv1.PrometheusSpec{
					CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
						Version:                            tc.version,
						EnableRemoteWriteReceiver:          tc.enableRemoteWriteReceiver,
						RemoteWriteReceiverMessageVersions: tc.messageVersions,
					},
				},
			}
			sset, err := makeStatefulSetFromPrometheus(p)
			require.NoError(t, err)

			var (
				enabled         bool
				messageVersions string
			)
			for _, flag := range sset.Spec.Template.Spec.Containers[0].Args {
				flag = strings.TrimPrefix(flag, "--")
				values := strings.Split(flag, "=")
				switch values[0] {
				case "web.enable-remote-write-receiver":
					enabled = true
				case "web.remote-write-receiver.accepted-protobuf-messages":
					messageVersions = values[1]
				}
			}

			require.Equal(t, tc.expectedRemoteWriteReceiverFlag, enabled)
			require.Equal(t, tc.expectedMessageVersions, messageVersions)
		})
	}
}

func TestPodTemplateConfig(t *testing.T) {
	nodeSelector := map[string]string{
		"foo": "bar",
	}
	affinity := v1.Affinity{
		NodeAffinity: &v1.NodeAffinity{},
		PodAffinity: &v1.PodAffinity{
			PreferredDuringSchedulingIgnoredDuringExecution: []v1.WeightedPodAffinityTerm{
				{
					PodAffinityTerm: v1.PodAffinityTerm{
						Namespaces: []string{"foo"},
					},
					Weight: 100,
				},
			},
		},
		PodAntiAffinity: &v1.PodAntiAffinity{},
	}

	tolerations := []v1.Toleration{
		{
			Key: "key",
		},
	}
	userid := int64(1234)
	securityContext := v1.PodSecurityContext{
		RunAsUser: &userid,
	}
	priorityClassName := "foo"
	serviceAccountName := "prometheus-sa"
	hostAliases := []monitoringv1.HostAlias{
		{
			Hostnames: []string{"foo.com"},
			IP:        "1.1.1.1",
		},
	}
	imagePullPolicy := v1.PullAlways
	imagePullSecrets := []v1.LocalObjectReference{
		{
			Name: "registry-secret",
		},
	}

	hostNetwork := false

	sset, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{
		ObjectMeta: metav1.ObjectMeta{},
		Spec: monitoringv1.PrometheusSpec{
			CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
				NodeSelector:       nodeSelector,
				Affinity:           &affinity,
				Tolerations:        tolerations,
				SecurityContext:    &securityContext,
				PriorityClassName:  priorityClassName,
				ServiceAccountName: serviceAccountName,
				HostAliases:        hostAliases,
				ImagePullPolicy:    imagePullPolicy,
				ImagePullSecrets:   imagePullSecrets,
				HostNetwork:        hostNetwork,
			},
		},
	})
	require.NoError(t, err)

	require.Equal(t, nodeSelector, sset.Spec.Template.Spec.NodeSelector, "expected node selector to match, want %v, got %v", nodeSelector, sset.Spec.Template.Spec.NodeSelector)
	require.Equal(t, affinity, *sset.Spec.Template.Spec.Affinity, "expected affinity to match, want %v, got %v", affinity, *sset.Spec.Template.Spec.Affinity)
	require.Equal(t, tolerations, sset.Spec.Template.Spec.Tolerations, "expected tolerations to match, want %v, got %v", tolerations, sset.Spec.Template.Spec.Tolerations)
	require.Equal(t, securityContext, *sset.Spec.Template.Spec.SecurityContext, "expected security context  to match, want %v, got %v", securityContext, *sset.Spec.Template.Spec.SecurityContext)
	require.Equal(t, priorityClassName, sset.Spec.Template.Spec.PriorityClassName, "expected priority class name to match, want %s, got %s", priorityClassName, sset.Spec.Template.Spec.PriorityClassName)
	require.Equal(t, serviceAccountName, sset.Spec.Template.Spec.ServiceAccountName, "expected service account name to match, want %s, got %s", serviceAccountName, sset.Spec.Template.Spec.ServiceAccountName)
	require.Len(t, sset.Spec.Template.Spec.HostAliases, len(hostAliases), "expected length of host aliases to match, want %d, got %d", len(hostAliases), len(sset.Spec.Template.Spec.HostAliases))
	for _, initContainer := range sset.Spec.Template.Spec.InitContainers {
		require.Equal(t, imagePullPolicy, initContainer.ImagePullPolicy, "expected imagePullPolicy to match, want %s, got %s", imagePullPolicy, initContainer.ImagePullPolicy)
	}
	for _, container := range sset.Spec.Template.Spec.Containers {
		require.Equal(t, imagePullPolicy, container.ImagePullPolicy, "expected imagePullPolicy to match, want %s, got %s", imagePullPolicy, container.ImagePullPolicy)
	}
	require.Equal(t, imagePullSecrets, sset.Spec.Template.Spec.ImagePullSecrets, "expected image pull secrets to match, want %s, got %s", imagePullSecrets, sset.Spec.Template.Spec.ImagePullSecrets)
	require.Equal(t, hostNetwork, sset.Spec.Template.Spec.HostNetwork, "expected hostNetwork configuration to match but failed")
}

func TestPrometheusAdditionalArgsNoError(t *testing.T) {
	expectedPrometheusArgs := []string{
		"--web.console.templates=/etc/prometheus/consoles",
		"--web.console.libraries=/etc/prometheus/console_libraries",
		"--config.file=/etc/prometheus/config_out/prometheus.env.yaml",
		"--web.enable-lifecycle",
		"--web.route-prefix=/",
		"--storage.tsdb.retention.time=24h",
		"--storage.tsdb.path=/prometheus",
		"--web.config.file=/etc/prometheus/web_config/web-config.yaml",
		"--scrape.discovery-reload-interval=30s",
		"--storage.tsdb.no-lockfile",
	}

	labels := map[string]string{
		"testlabel": "testlabelvalue",
	}
	annotations := map[string]string{
		"testannotation": "testannotationvalue",
	}

	sset, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{
		ObjectMeta: metav1.ObjectMeta{
			Labels:      labels,
			Annotations: annotations,
		},
		Spec: monitoringv1.PrometheusSpec{
			CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
				AdditionalArgs: []monitoringv1.Argument{
					{
						Name:  "scrape.discovery-reload-interval",
						Value: "30s",
					},
					{
						Name: "storage.tsdb.no-lockfile",
					},
				},
			},
		},
	})
	require.NoError(t, err)

	ssetContainerArgs := sset.Spec.Template.Spec.Containers[0].Args
	require.Equal(t, expectedPrometheusArgs, ssetContainerArgs, "expected Prometheus container args to match, want %s, got %s", expectedPrometheusArgs, ssetContainerArgs)
}

func TestPrometheusAdditionalArgsDuplicate(t *testing.T) {
	expectedErrorMsg := "can't set arguments which are already managed by the operator: config.file"

	labels := map[string]string{
		"testlabel": "testlabelvalue",
	}
	annotations := map[string]string{
		"testannotation": "testannotationvalue",
	}

	_, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{
		ObjectMeta: metav1.ObjectMeta{
			Labels:      labels,
			Annotations: annotations,
		},
		Spec: monitoringv1.PrometheusSpec{
			CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
				AdditionalArgs: []monitoringv1.Argument{
					{
						Name:  "config.file",
						Value: "/foo/bar.yaml",
					},
				},
			},
		},
	})
	require.Error(t, err)

	require.Contains(t, err.Error(), expectedErrorMsg, "expected the following text to be present in the error msg: %s", expectedErrorMsg)
}

func TestRuntimeGOGCEnvVar(t *testing.T) {
	for _, tc := range []struct {
		scenario       string
		version        string
		gogc           *int32
		expectedEnvVar bool
	}{
		{
			scenario:       "Prometheus < 2.53.0",
			version:        "v2.51.2",
			gogc:           ptr.To(int32(50)),
			expectedEnvVar: true,
		},
		{
			scenario:       "Prometheus > 2.53.0",
			version:        "v2.54.0",
			gogc:           ptr.To(int32(50)),
			expectedEnvVar: false,
		},
	} {
		t.Run(fmt.Sprintf("case %s", tc.scenario), func(t *testing.T) {
			ss, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{
				Spec: monitoringv1.PrometheusSpec{
					CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
						Version: tc.version,
						Runtime: &monitoringv1.RuntimeConfig{
							GoGC: tc.gogc,
						},
					},
				},
			})

			var containsEnvVar bool
			for _, env := range ss.Spec.Template.Spec.Containers[0].Env {
				if env.Name == "GOGC" {
					if env.Value == fmt.Sprintf("%d", *tc.gogc) {
						containsEnvVar = true
						break
					}
				}
			}

			require.NoError(t, err)
			if tc.expectedEnvVar {
				require.True(t, containsEnvVar, "Prometheus is missing expected GOGC env var with correct value")
			}

			if !tc.expectedEnvVar {
				require.False(t, containsEnvVar, "Prometheus didn't expect GOGC env var for this version of Prometheus")
			}
		})
	}
}

func TestPrometheusAdditionalBinaryArgsDuplicate(t *testing.T) {
	expectedErrorMsg := "can't set arguments which are already managed by the operator: web.enable-lifecycle"

	labels := map[string]string{
		"testlabel": "testlabelvalue",
	}
	annotations := map[string]string{
		"testannotation": "testannotationvalue",
	}

	_, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{
		ObjectMeta: metav1.ObjectMeta{
			Labels:      labels,
			Annotations: annotations,
		},
		Spec: monitoringv1.PrometheusSpec{
			CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
				AdditionalArgs: []monitoringv1.Argument{
					{
						Name: "web.enable-lifecycle",
					},
				},
			},
		},
	})
	require.Error(t, err)

	require.Contains(t, err.Error(), expectedErrorMsg, "expected the following text to be present in the error msg: %s", expectedErrorMsg)
}

func TestPrometheusAdditionalNoPrefixArgsDuplicate(t *testing.T) {
	expectedErrorMsg := "can't set arguments which are already managed by the operator: no-storage.tsdb.wal-compression"
	walCompression := new(bool)
	*walCompression = true

	labels := map[string]string{
		"testlabel": "testlabelvalue",
	}
	annotations := map[string]string{
		"testannotation": "testannotationvalue",
	}

	_, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{
		ObjectMeta: metav1.ObjectMeta{
			Labels:      labels,
			Annotations: annotations,
		},
		Spec: monitoringv1.PrometheusSpec{
			CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
				WALCompression: walCompression,
				AdditionalArgs: []monitoringv1.Argument{
					{
						Name: "no-storage.tsdb.wal-compression",
					},
				},
			},
		},
	})
	require.Error(t, err)

	require.Contains(t, err.Error(), expectedErrorMsg, "expected the following text to be present in the error msg: %s", expectedErrorMsg)
}

func TestThanosAdditionalArgsNoError(t *testing.T) {
	expectedThanosArgs := []string{
		"sidecar",
		"--prometheus.url=http://localhost:9090/",
		"--grpc-address=:10901",
		"--http-address=:10902",
		"--log.level=info",
		"--prometheus.http-client-file=/etc/thanos/config/prometheus.http-client-file.yaml",
		"--reloader.watch-interval=5m",
	}

	labels := map[string]string{
		"testlabel": "testlabelvalue",
	}
	annotations := map[string]string{
		"testannotation": "testannotationvalue",
	}

	sset, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{
		ObjectMeta: metav1.ObjectMeta{
			Labels:      labels,
			Annotations: annotations,
		},
		Spec: monitoringv1.PrometheusSpec{
			Thanos: &monitoringv1.ThanosSpec{
				LogLevel: "info",
				AdditionalArgs: []monitoringv1.Argument{
					{
						Name:  "reloader.watch-interval",
						Value: "5m",
					},
				},
			},
		},
	})
	require.NoError(t, err)

	ssetContainerArgs := sset.Spec.Template.Spec.Containers[2].Args
	require.Equal(t, expectedThanosArgs, ssetContainerArgs, "expected Thanos container args to match, want %s, got %s", expectedThanosArgs, ssetContainerArgs)
}

func TestThanosAdditionalArgsDuplicate(t *testing.T) {
	expectedErrorMsg := "can't set arguments which are already managed by the operator: log.level"

	labels := map[string]string{
		"testlabel": "testlabelvalue",
	}
	annotations := map[string]string{
		"testannotation": "testannotationvalue",
	}

	_, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{
		ObjectMeta: metav1.ObjectMeta{
			Labels:      labels,
			Annotations: annotations,
		},
		Spec: monitoringv1.PrometheusSpec{
			Thanos: &monitoringv1.ThanosSpec{
				LogLevel: "info",
				AdditionalArgs: []monitoringv1.Argument{
					{
						Name:  "log.level",
						Value: "error",
					},
				},
			},
		},
	})
	require.Error(t, err)

	require.Contains(t, err.Error(), expectedErrorMsg, "expected the following text to be present in the error msg: %s", expectedErrorMsg)
}

func TestPrometheusQuerySpec(t *testing.T) {
	for _, tc := range []struct {
		name string

		lookbackDelta  *string
		maxConcurrency *int32
		maxSamples     *int32
		timeout        *monitoringv1.Duration
		version        string

		expected []string
	}{
		{
			name:     "default",
			expected: []string{},
		},
		{
			name:           "all values provided",
			lookbackDelta:  ptr.To("2m"),
			maxConcurrency: ptr.To(int32(10)),
			maxSamples:     ptr.To(int32(10000)),
			timeout:        ptr.To(monitoringv1.Duration("1m")),

			expected: []string{
				"--query.lookback-delta=2m",
				"--query.max-concurrency=10",
				"--query.max-samples=10000",
				"--query.timeout=1m",
			},
		},
		{
			name:           "zero values are skipped",
			lookbackDelta:  ptr.To("2m"),
			maxConcurrency: ptr.To(int32(0)),
			maxSamples:     ptr.To(int32(0)),
			timeout:        ptr.To(monitoringv1.Duration("1m")),

			expected: []string{
				"--query.lookback-delta=2m",
				"--query.timeout=1m",
			},
		},
		{
			name:           "max samples skipped if version < 2.5",
			lookbackDelta:  ptr.To("2m"),
			maxConcurrency: ptr.To(int32(10)),
			maxSamples:     ptr.To(int32(10000)),
			timeout:        ptr.To(monitoringv1.Duration("1m")),
			version:        "v2.4.0",

			expected: []string{
				"--query.lookback-delta=2m",
				"--query.max-concurrency=10",
				"--query.timeout=1m",
			},
		},
		{
			name:           "max samples not skipped if version > 2.5",
			lookbackDelta:  ptr.To("2m"),
			maxConcurrency: ptr.To(int32(10)),
			maxSamples:     ptr.To(int32(10000)),
			timeout:        ptr.To(monitoringv1.Duration("1m")),
			version:        "v2.5.0",

			expected: []string{
				"--query.lookback-delta=2m",
				"--query.max-concurrency=10",
				"--query.max-samples=10000",
				"--query.timeout=1m",
			},
		},
	} {
		t.Run(tc.name, func(t *testing.T) {
			sset, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{
				Spec: monitoringv1.PrometheusSpec{
					CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
						Version: tc.version,
					},
					Query: &monitoringv1.QuerySpec{
						LookbackDelta:  tc.lookbackDelta,
						MaxConcurrency: tc.maxConcurrency,
						MaxSamples:     tc.maxSamples,
						Timeout:        tc.timeout,
					},
				},
			})
			require.NoError(t, err)

			for _, arg := range []string{
				"--query.lookback-delta",
				"--query.max-concurrency",
				"--query.max-samples",
				"--query.timeout",
			} {
				var containerArg string
				for _, a := range sset.Spec.Template.Spec.Containers[0].Args {
					if strings.HasPrefix(a, arg) {
						containerArg = a
						break
					}
				}

				var expected string
				for _, exp := range tc.expected {
					if strings.HasPrefix(exp, arg) {
						expected = exp
						break
					}
				}

				if expected == "" {
					require.Equal(t, "", containerArg, "found %q while not expected", containerArg)
					continue
				}

				require.Equal(t, expected, containerArg, "expected %q to be found but got %q", expected, containerArg)
			}
		})
	}
}

func TestSecurityContextCapabilities(t *testing.T) {
	for _, tc := range []struct {
		name string
		spec monitoringv1.PrometheusSpec
	}{
		{
			name: "default",
			spec: monitoringv1.PrometheusSpec{},
		},
		{
			name: "Thanos sidecar",
			spec: monitoringv1.PrometheusSpec{
				Thanos: &monitoringv1.ThanosSpec{},
			},
		},
		{
			name: "Thanos sidecar with object storage",
			spec: monitoringv1.PrometheusSpec{
				Thanos: &monitoringv1.ThanosSpec{
					ObjectStorageConfigFile: ptr.To("/etc/thanos.cfg"),
				},
			},
		},
	} {
		t.Run(tc.name, func(t *testing.T) {
			sset, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{Spec: tc.spec})
			require.NoError(t, err)

			exp := 2
			if tc.spec.Thanos != nil {
				exp++
			}
			require.Len(t, sset.Spec.Template.Spec.Containers, exp, "Expecting %d containers, got %d", exp, len(sset.Spec.Template.Spec.Containers))

			for _, c := range sset.Spec.Template.Spec.Containers {
				require.Empty(t, c.SecurityContext.Capabilities.Add, "Expecting 0 added capabilities, got %d", len(c.SecurityContext.Capabilities.Add))
				require.Len(t, c.SecurityContext.Capabilities.Drop, 1, "Expecting 1 dropped capabilities, got %d", len(c.SecurityContext.Capabilities.Drop))

				require.Equal(t, "ALL", string(c.SecurityContext.Capabilities.Drop[0]), "Expecting ALL dropped capability, got %s", c.SecurityContext.Capabilities.Drop[0])
			}
		})
	}
}

func TestPodHostNetworkConfig(t *testing.T) {
	hostNetwork := true
	sset, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{
		ObjectMeta: metav1.ObjectMeta{},
		Spec: monitoringv1.PrometheusSpec{
			CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
				HostNetwork: hostNetwork,
			},
		},
	})
	require.NoError(t, err)

	require.Equal(t, hostNetwork, sset.Spec.Template.Spec.HostNetwork, "expected hostNetwork configuration to match but failed")
	require.Equal(t, v1.DNSClusterFirstWithHostNet, sset.Spec.Template.Spec.DNSPolicy, "expected DNSPolicy configuration to match due to hostNetwork but failed")
}

func TestPersistentVolumeClaimRetentionPolicy(t *testing.T) {
	sset, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{
		ObjectMeta: metav1.ObjectMeta{},
		Spec: monitoringv1.PrometheusSpec{
			CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
				PersistentVolumeClaimRetentionPolicy: &appsv1.StatefulSetPersistentVolumeClaimRetentionPolicy{
					WhenDeleted: appsv1.DeletePersistentVolumeClaimRetentionPolicyType,
					WhenScaled:  appsv1.DeletePersistentVolumeClaimRetentionPolicyType,
				},
			},
		},
	})
	require.NoError(t, err)

	require.Equal(t, appsv1.DeletePersistentVolumeClaimRetentionPolicyType, sset.Spec.PersistentVolumeClaimRetentionPolicy.WhenDeleted, "expected persistentVolumeClaimDeletePolicy.WhenDeleted to be %s but got %s", appsv1.DeletePersistentVolumeClaimRetentionPolicyType, sset.Spec.PersistentVolumeClaimRetentionPolicy.WhenDeleted)
	require.Equal(t, appsv1.DeletePersistentVolumeClaimRetentionPolicyType, sset.Spec.PersistentVolumeClaimRetentionPolicy.WhenScaled, "expected persistentVolumeClaimDeletePolicy.WhenScaled to be %s but got %s", appsv1.DeletePersistentVolumeClaimRetentionPolicyType, sset.Spec.PersistentVolumeClaimRetentionPolicy.WhenScaled)
}

func TestPodTopologySpreadConstraintWithAdditionalLabels(t *testing.T) {
	for _, tc := range []struct {
		name string
		spec monitoringv1.PrometheusSpec
		tsc  v1.TopologySpreadConstraint
	}{
		{
			name: "without labelSelector and additionalLabels",
			spec: monitoringv1.PrometheusSpec{
				CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
					TopologySpreadConstraints: []monitoringv1.TopologySpreadConstraint{
						{
							CoreV1TopologySpreadConstraint: monitoringv1.CoreV1TopologySpreadConstraint{
								MaxSkew:           1,
								TopologyKey:       "kubernetes.io/hostname",
								WhenUnsatisfiable: v1.DoNotSchedule,
							},
						},
					},
				},
			},
			tsc: v1.TopologySpreadConstraint{
				MaxSkew:           1,
				TopologyKey:       "kubernetes.io/hostname",
				WhenUnsatisfiable: v1.DoNotSchedule,
			},
		},
		{
			name: "with labelSelector and without additionalLabels",
			spec: monitoringv1.PrometheusSpec{
				CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
					TopologySpreadConstraints: []monitoringv1.TopologySpreadConstraint{
						{
							CoreV1TopologySpreadConstraint: monitoringv1.CoreV1TopologySpreadConstraint{
								MaxSkew:           1,
								TopologyKey:       "kubernetes.io/hostname",
								WhenUnsatisfiable: v1.DoNotSchedule,
								LabelSelector: &metav1.LabelSelector{
									MatchLabels: map[string]string{
										"app": "prometheus",
									},
								},
							},
						},
					},
				},
			},
			tsc: v1.TopologySpreadConstraint{
				MaxSkew:           1,
				TopologyKey:       "kubernetes.io/hostname",
				WhenUnsatisfiable: v1.DoNotSchedule,
				LabelSelector: &metav1.LabelSelector{
					MatchLabels: map[string]string{
						"app": "prometheus",
					},
				},
			},
		},
		{
			name: "with labelSelector and additionalLabels as ShardAndNameResource",
			spec: monitoringv1.PrometheusSpec{
				CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
					TopologySpreadConstraints: []monitoringv1.TopologySpreadConstraint{
						{
							AdditionalLabelSelectors: ptr.To(monitoringv1.ShardAndResourceNameLabelSelector),
							CoreV1TopologySpreadConstraint: monitoringv1.CoreV1TopologySpreadConstraint{
								MaxSkew:           1,
								TopologyKey:       "kubernetes.io/hostname",
								WhenUnsatisfiable: v1.DoNotSchedule,
								LabelSelector: &metav1.LabelSelector{
									MatchLabels: map[string]string{
										"app": "prometheus",
									},
								},
							},
						},
					},
				},
			},
			tsc: v1.TopologySpreadConstraint{
				MaxSkew:           1,
				TopologyKey:       "kubernetes.io/hostname",
				WhenUnsatisfiable: v1.DoNotSchedule,
				LabelSelector: &metav1.LabelSelector{
					MatchLabels: map[string]string{
						"app":                           "prometheus",
						"app.kubernetes.io/instance":    "test",
						"app.kubernetes.io/managed-by":  "prometheus-operator",
						"prometheus":                    "test",
						prompkg.ShardLabelName:          "0",
						prompkg.PrometheusNameLabelName: "test",
						prompkg.PrometheusK8sLabelName:  "prometheus",
					},
				},
			},
		},
		{
			name: "with labelSelector and additionalLabels as ResourceName",
			spec: monitoringv1.PrometheusSpec{
				CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
					TopologySpreadConstraints: []monitoringv1.TopologySpreadConstraint{
						{
							AdditionalLabelSelectors: ptr.To(monitoringv1.ResourceNameLabelSelector),
							CoreV1TopologySpreadConstraint: monitoringv1.CoreV1TopologySpreadConstraint{
								MaxSkew:           1,
								TopologyKey:       "kubernetes.io/hostname",
								WhenUnsatisfiable: v1.DoNotSchedule,
								LabelSelector: &metav1.LabelSelector{
									MatchLabels: map[string]string{
										"app": "prometheus",
									},
								},
							},
						},
					},
				},
			},
			tsc: v1.TopologySpreadConstraint{
				MaxSkew:           1,
				TopologyKey:       "kubernetes.io/hostname",
				WhenUnsatisfiable: v1.DoNotSchedule,
				LabelSelector: &metav1.LabelSelector{
					MatchLabels: map[string]string{
						"app":                           "prometheus",
						"app.kubernetes.io/instance":    "test",
						"app.kubernetes.io/managed-by":  "prometheus-operator",
						"prometheus":                    "test",
						prompkg.PrometheusNameLabelName: "test",
						prompkg.PrometheusK8sLabelName:  "prometheus",
					},
				},
			},
		},
	} {
		t.Run(tc.name, func(t *testing.T) {
			sts, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "test",
					Namespace: "ns-test",
				},
				Spec: tc.spec,
			})

			require.NoError(t, err)

			assert.NotEmpty(t, sts.Spec.Template.Spec.TopologySpreadConstraints)
			assert.Equal(t, tc.tsc, sts.Spec.Template.Spec.TopologySpreadConstraints[0])
		})
	}
}

func TestStartupProbeTimeoutSeconds(t *testing.T) {
	tests := []struct {
		maximumStartupDurationSeconds   *int32
		expectedStartupPeriodSeconds    int32
		expectedStartupFailureThreshold int32
	}{
		{
			maximumStartupDurationSeconds:   nil,
			expectedStartupPeriodSeconds:    15,
			expectedStartupFailureThreshold: 60,
		},
		{
			maximumStartupDurationSeconds:   ptr.To(int32(600)),
			expectedStartupPeriodSeconds:    60,
			expectedStartupFailureThreshold: 10,
		},
	}

	for _, test := range tests {
		sset, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{
			ObjectMeta: metav1.ObjectMeta{},
			Spec: monitoringv1.PrometheusSpec{
				CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
					MaximumStartupDurationSeconds: test.maximumStartupDurationSeconds,
				},
			},
		})

		require.NoError(t, err)
		require.NotNil(t, sset.Spec.Template.Spec.Containers[0].StartupProbe)
		require.Equal(t, test.expectedStartupPeriodSeconds, sset.Spec.Template.Spec.Containers[0].StartupProbe.PeriodSeconds)
		require.Equal(t, test.expectedStartupFailureThreshold, sset.Spec.Template.Spec.Containers[0].StartupProbe.FailureThreshold)
	}
}

func TestIfThanosVersionDontHaveHttpClientFlag(t *testing.T) {
	version := "v0.23.0"

	for _, tc := range []struct {
		name string
		spec monitoringv1.PrometheusSpec
	}{
		{
			name: "default",
			spec: monitoringv1.PrometheusSpec{},
		},
		{
			name: "Thanos sidecar",
			spec: monitoringv1.PrometheusSpec{
				Thanos: &monitoringv1.ThanosSpec{
					Version: &version,
				},
			},
		},
	} {
		t.Run(tc.name, func(t *testing.T) {
			sset, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{Spec: tc.spec})
			require.NoError(t, err)
			for _, c := range sset.Spec.Template.Spec.Containers {
				for _, arg := range c.Args {
					require.NotContains(t, arg, "http-client", "Expecting http-client flag to not be present in Thanos sidecar")
				}
			}
		})
	}
}

func TestThanosWithPrometheusHTTPClientConfigFile(t *testing.T) {
	version := "0.24.0"

	for _, tc := range []struct {
		name string
		spec monitoringv1.PrometheusSpec
	}{
		{
			name: "thanos sidecar with prometheus.http-client-file",
			spec: monitoringv1.PrometheusSpec{
				Thanos: &monitoringv1.ThanosSpec{
					Version: &version,
				},
			},
		},
	} {
		t.Run(tc.name, func(t *testing.T) {
			p := monitoringv1.Prometheus{Spec: tc.spec}
			sset, err := makeStatefulSetFromPrometheus(p)
			require.NoError(t, err)
			for _, v := range sset.Spec.Template.Spec.Volumes {
				if v.Name == thanosPrometheusHTTPClientConfigSecretNameSuffix {
					require.Equal(t, v.VolumeSource.Secret.SecretName, thanosPrometheusHTTPClientConfigSecretName(&p))
				}
			}
			for _, c := range sset.Spec.Template.Spec.Containers {
				if c.Name == "thanos-sidecar" {
					require.NotEmpty(t, c.VolumeMounts)
					require.Equal(t, thanosPrometheusHTTPClientConfigSecretNameSuffix, c.VolumeMounts[0].Name)
				}
			}
		})
	}
}

func TestAutomountServiceAccountToken(t *testing.T) {
	for _, tc := range []struct {
		name                         string
		automountServiceAccountToken *bool
		expectedValue                bool
	}{
		{
			name:                         "automountServiceAccountToken not set",
			automountServiceAccountToken: nil,
			expectedValue:                true,
		},
		{
			name:                         "automountServiceAccountToken set to true",
			automountServiceAccountToken: ptr.To(true),
			expectedValue:                true,
		},
		{
			name:                         "automountServiceAccountToken set to false",
			automountServiceAccountToken: ptr.To(false),
			expectedValue:                false,
		},
	} {
		t.Run(tc.name, func(t *testing.T) {
			sset, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{
				ObjectMeta: metav1.ObjectMeta{},
				Spec: monitoringv1.PrometheusSpec{
					CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
						AutomountServiceAccountToken: tc.automountServiceAccountToken,
					},
				},
			})
			require.NoError(t, err)

			require.NotNil(t, sset.Spec.Template.Spec.AutomountServiceAccountToken, "expected automountServiceAccountToken to be set")

			require.Equal(t, tc.expectedValue, *sset.Spec.Template.Spec.AutomountServiceAccountToken, "expected automountServiceAccountToken to be %v", tc.expectedValue)
		})
	}
}

func TestDNSPolicyAndDNSConfig(t *testing.T) {
	tests := []struct {
		name              string
		dnsPolicy         v1.DNSPolicy
		dnsConfig         *v1.PodDNSConfig
		expectedDNSPolicy v1.DNSPolicy
		expectedDNSConfig *v1.PodDNSConfig
	}{
		{
			name:              "Default DNSPolicy and DNSConfig",
			dnsPolicy:         v1.DNSClusterFirst,
			dnsConfig:         nil,
			expectedDNSPolicy: v1.DNSClusterFirst,
			expectedDNSConfig: nil,
		},
		{
			name:              "Custom DNSPolicy",
			dnsPolicy:         v1.DNSDefault,
			dnsConfig:         nil,
			expectedDNSPolicy: v1.DNSDefault,
			expectedDNSConfig: nil,
		},
		{
			name:      "Custom DNSConfig",
			dnsPolicy: v1.DNSClusterFirst,
			dnsConfig: &v1.PodDNSConfig{
				Nameservers: []string{"8.8.8.8", "8.8.4.4"},
				Searches:    []string{"custom.svc.cluster.local"},
			},
			expectedDNSPolicy: v1.DNSClusterFirst,
			expectedDNSConfig: &v1.PodDNSConfig{
				Nameservers: []string{"8.8.8.8", "8.8.4.4"},
				Searches:    []string{"custom.svc.cluster.local"},
			},
		},
		{
			name:      "Custom DNS Policy with Search Domains",
			dnsPolicy: v1.DNSDefault,
			dnsConfig: &v1.PodDNSConfig{
				Searches: []string{"kitsos.com", "kitsos.org"},
			},
			expectedDNSPolicy: v1.DNSDefault,
			expectedDNSConfig: &v1.PodDNSConfig{
				Searches: []string{"kitsos.com", "kitsos.org"},
			},
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			monitoringDNSPolicyPtr := ptr.To(monitoringv1.DNSPolicy(test.dnsPolicy))

			var monitoringDNSConfig *monitoringv1.PodDNSConfig
			if test.dnsConfig != nil {
				monitoringDNSConfig = &monitoringv1.PodDNSConfig{
					Nameservers: test.dnsConfig.Nameservers,
					Searches:    test.dnsConfig.Searches,
				}
			}

			sset, err := makeStatefulSetFromPrometheus(monitoringv1.Prometheus{
				ObjectMeta: metav1.ObjectMeta{},
				Spec: monitoringv1.PrometheusSpec{
					CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
						DNSPolicy: monitoringDNSPolicyPtr,
						DNSConfig: monitoringDNSConfig,
					},
				},
			})
			require.NoError(t, err)

			require.Equal(t, test.expectedDNSPolicy, sset.Spec.Template.Spec.DNSPolicy, "expected DNSPolicy to match, want %v, got %v", test.expectedDNSPolicy, sset.Spec.Template.Spec.DNSPolicy)
			if test.expectedDNSConfig != nil {
				require.NotNil(t, sset.Spec.Template.Spec.DNSConfig, "expected DNSConfig to be set")
				require.Equal(t, test.expectedDNSConfig.Nameservers, sset.Spec.Template.Spec.DNSConfig.Nameservers, "expected DNSConfig Nameservers to match, want %v, got %v", test.expectedDNSConfig.Nameservers, sset.Spec.Template.Spec.DNSConfig.Nameservers)
				require.Equal(t, test.expectedDNSConfig.Searches, sset.Spec.Template.Spec.DNSConfig.Searches, "expected DNSConfig Searches to match, want %v, got %v", test.expectedDNSConfig.Searches, sset.Spec.Template.Spec.DNSConfig.Searches)
			} else {
				require.Nil(t, sset.Spec.Template.Spec.DNSConfig, "expected DNSConfig to be nil")
			}
		})
	}
}
